确定代码是否作为单元测试的一部分运行


105

我有一个单元测试(nUnit)。如果方法通过单元测试运行,则调用堆栈的许多层都将失败。

理想情况下,您将使用诸如模拟之类的方法来设置此方法所依赖的对象,但这是第三方代码,如果没有很多工作,我将无法做到这一点。

我不想设置特定于nUnit的方法-这里的级别太多了,这是进行单元测试的一种糟糕方法。

相反,我想做的就是在调用堆栈中添加类似的内容

#IF DEBUG // Unit tests only included in debug build
if (IsRunningInUnitTest)
   {
   // Do some setup to avoid error
   }
#endif

那么关于如何编写IsRunningInUnitTest的任何想法吗?

PS我完全意识到这不是一个很好的设计,但是我认为它比其他选择更好。


5
您不应在单元测试中直接或间接测试第三方代码。您应该将测试的方法与第三方实现隔离。
Craig Stuntz

14
是的-我意识到-在思想世界中,但是有时候我们必须对某些事情有些务实,不是吗?
瑞安

9
回到Craig的评论-不确定那是真的。如果我的方法以某种方式依赖于第三方库的行为,那么这不应该成为测试的一部分吗?如果第三方应用程序更改,我希望我的测试失败。如果您使用模拟,则针对您认为第三方应用程序的工作方式进行测试,而不是针对其实际工作方式进行测试。
瑞安(Ryan)2010年

2
Ryan,您可以测试有关第三方行为的假设,但这是一个单独的测试。您需要单独测试自己的代码。
Craig Stuntz 2010年

2
我确实得到您的意思,但是对于一个琐碎的例子,您将谈论的是大量工作,并且没有任何东西可以确保测试中的假设与实际方法中的假设相同。嗯-辩论博客文章,我想,我一想起就给您发电子邮件。
瑞安(Ryan)2010年

Answers:


80

我以前曾经做过-我在做的同时必须握住鼻子,但我做到了。实用主义总是战胜教条主义。当然,如果一个很好的方式,你可以重构,以避免它,那将是巨大的。

基本上,我有一个“ UnitTestDetector”类,用于检查NUnit框架程序集是否已加载到当前AppDomain中。它只需要执行一次,然后缓存结果。丑陋,但简单有效。


关于UnitTestDetector的任何示例?和MSTest类似吗?
Kiquenet

4
@Kiquenet:我想我只是使用AppDomain.GetAssemblies并检查相关的程序集-对于MSTest,您需要查看加载了哪些程序集。以Ryan的答案为例。
乔恩·斯基特

这对我来说不是一个好方法。我从控制台应用程序调用UnitTest方法,它认为这是一个UnitTest应用程序。
Bizhan

1
@Bizhan:我建议您当时处于一种相当专业的情况,并且您不应该期望得到更一般的答案。您可能想对所有特定要求提出一个新问题。(例如,“从控制台应用程序调用代码”和“测试运行程序”之间有什么区别?您如何区分控制台应用程序和任何其他基于控制台的测试运行程序?)
Jon Skeet

74

以乔恩的想法,这就是我想出的-

using System;
using System.Reflection;

/// <summary>
/// Detect if we are running as part of a nUnit unit test.
/// This is DIRTY and should only be used if absolutely necessary 
/// as its usually a sign of bad design.
/// </summary>    
static class UnitTestDetector
{

    private static bool _runningFromNUnit = false;      

    static UnitTestDetector()
    {
        foreach (Assembly assem in AppDomain.CurrentDomain.GetAssemblies())
        {
            // Can't do something like this as it will load the nUnit assembly
            // if (assem == typeof(NUnit.Framework.Assert))

            if (assem.FullName.ToLowerInvariant().StartsWith("nunit.framework"))
            {
                _runningFromNUnit = true;
                break;
            }
        }
    }

    public static bool IsRunningFromNUnit
    {
        get { return _runningFromNUnit; }
    }
}

管我们后面的人都足够大,可以认出我们在做什么时可能不应该做的事情;)


2
+1好答案。不过,可以将其简化很多,如下所示:stackoverflow.com/a/30356080/184528
cdiggins 2015年

我为此编写的特定项目是(现在仍然是!)。NET 2.0,所以没有linq。
瑞安

这种用法对我来说很有效,但似乎程序集名称此后已更改。我切换到Kiquenet的解决方案
The_Black_Smurf

我不得不关闭travis ci版本的日志记录,这冻结了所有内容
jjxtra

对我有用,我必须用剃刀解决.NET核心3个仅在单元测试中发生的错误。
jjxtra

62

改编自瑞安的答案。这是针对MS单元测试框架的。

我需要这个的原因是因为我在错误中显示了一个MessageBox。但是我的单元测试也测试错误处理代码,并且我不希望在运行单元测试时弹出MessageBox。

/// <summary>
/// Detects if we are running inside a unit test.
/// </summary>
public static class UnitTestDetector
{
    static UnitTestDetector()
    {
        string testAssemblyName = "Microsoft.VisualStudio.QualityTools.UnitTestFramework";
        UnitTestDetector.IsInUnitTest = AppDomain.CurrentDomain.GetAssemblies()
            .Any(a => a.FullName.StartsWith(testAssemblyName));
    }

    public static bool IsInUnitTest { get; private set; }
}

这是它的单元测试:

    [TestMethod]
    public void IsInUnitTest()
    {
        Assert.IsTrue(UnitTestDetector.IsInUnitTest, 
            "Should detect that we are running inside a unit test."); // lol
    }

8
我有一个更好的方法来解决您的MessageBox问题,并使此hack无效并提供更多的单元测试用例。我使用的类实现了称为ICommonDialogs的接口。实现类显示所有弹出对话框(MessageBox,File对话框,Color Picker,数据库连接对话框等)。需要显示消息框的类接受ICommonDiaglogs作为构造函数参数,然后可以在单元测试中对其进行模拟。奖励:您可以对预期的MessageBox调用进行断言。
Tony O'Hagan 2012年

1
@Tony,好主意。显然,这是最好的方法。我当时不知道我在想什么。我认为当时依赖注入对于我来说仍然是新事物。
dan-gph 2013年

3
认真地,人们了解依赖注入,其次是模拟对象。依赖注入将彻底改变您的编程。
dan-gph 2014年

2
我将实现UnitTestDetector.IsInUnitTest为“ return true”,并且您的单元测试将通过。;)那些似乎无法进行单元测试的有趣事情之一。
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework不再对我有用。将其更改为Microsoft.VisualStudio.TestPlatform.TestFramework-可以再次使用。
亚历山大

21

为简化Ryan的解决方案,您可以将以下静态属性添加到任何类中:

    public static readonly bool IsRunningFromNUnit = 
        AppDomain.CurrentDomain.GetAssemblies().Any(
            a => a.FullName.ToLowerInvariant().StartsWith("nunit.framework"));

2
与dan-gph的答案几乎相同(尽管那是在寻找VS工具集,而不是nunit)。
瑞安

18

我使用与塔尔斯泰斯类似的方法

这是基本代码,可以很容易地修改为包括缓存。另一个好主意是在项目的主要入口点添加一个setter IsRunningInUnitTest并调用它UnitTestDetector.IsRunningInUnitTest = false,以避免代码执行。

public static class UnitTestDetector
{
    public static readonly HashSet<string> UnitTestAttributes = new HashSet<string> 
    {
        "Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute",
        "NUnit.Framework.TestFixtureAttribute",
    };
    public static bool IsRunningInUnitTest
    {
        get
        {
            foreach (var f in new StackTrace().GetFrames())
                if (f.GetMethod().DeclaringType.GetCustomAttributes(false).Any(x => UnitTestAttributes.Contains(x.GetType().FullName)))
                    return true;
            return false;
        }
    }
}

我比投票更高的答案更喜欢这种方法。我认为,仅在单元测试期间才加载单元测试程序集是不安全的,并且进程名称也可能因开发人员而异(例如,某些使用R#测试运行程序)。
EM0

这种方法可以工作,但是每次调用IsRunningInUnitTest getter 时都会在寻找那些属性。在某些情况下,它可能会影响性能。检查AssemblyName比较便宜,因为它只完成了一次。使用公共设置器的想法很好,但是在这种情况下,应该将UnitTestDetector类放置在共享程序集中。
谢尔盖

13

检查当前的ProcessName可能有用:

public static bool UnitTestMode
{
    get 
    { 
        string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;

        return processName == "VSTestHost"
                || processName.StartsWith("vstest.executionengine") //it can be vstest.executionengine.x86 or vstest.executionengine.x86.clr20
                || processName.StartsWith("QTAgent");   //QTAgent32 or QTAgent32_35
    }
}

并且此功能还应通过unittest进行检查:

[TestClass]
public class TestUnittestRunning
{
    [TestMethod]
    public void UnitTestRunningTest()
    {
        Assert.IsTrue(MyTools.UnitTestMode);
    }
}

参考:http :
//social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/11e68468-c95e-4c43-b02b-7045a52b407e/中的Matthew Watson


|| processName.StartsWith("testhost") // testhost.x86VS 2019
Kiquenet

9

在测试模式下,Assembly.GetEntryAssembly()似乎是null

#IF DEBUG // Unit tests only included in debug build 
  if (Assembly.GetEntryAssembly() == null)    
  {
    // Do some setup to avoid error    
  }
#endif 

请注意,如果Assembly.GetEntryAssembly()nullAssembly.GetExecutingAssembly()则为。

文档说:从非托管应用程序加载托管程序集后,该GetEntryAssembly方法可以返回null


8

在要测试的项目中的某个位置:

public static class Startup
{
    public static bool IsRunningInUnitTest { get; set; }
}

在单元测试项目中的某个位置:

[TestClass]
public static class AssemblyInitializer
{
    [AssemblyInitialize]
    public static void Initialize(TestContext context)
    {
        Startup.IsRunningInUnitTest = true;
    }
}

优雅,不。但是直接又快速。AssemblyInitializer用于MS测试。我希望其他测试框架也有等效的框架。


1
如果您要测试的代码创建其他AppDomain,IsRunningInUnitTest则不会在这些AppDomain中将其设置为true。
爱德华·布雷

但是,可以通过在每个域中添加共享程序集或声明IsRunningInUnitTest来轻松解决。
谢尔盖

3

仅将其用于跳过逻辑,该逻辑在启动时未连接调试器时将禁用log4net中的所有TraceAppender。即使在非调试模式下运行,这也允许单元测试记录到“ Reshaper结果”窗口。

使用该函数的方法在应用程序启动时或在开始测试夹具时即被调用。

它与Ryan的帖子类似,但是使用LINQ,删除了System.Reflection要求,不缓存结果,并且是私有的,以防止(意外)滥用。

    private static bool IsNUnitRunning()
    {
        return AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.FullName.ToLowerInvariant().StartsWith("nunit.framework"));
    }

3

引用nunit框架并不意味着测试实际上正在运行。例如,在Unity中,当您激活播放模式测试时,会将nunit引用添加到项目中。并且当您运行游戏时,引用已经存在,因此UnitTestDetector无法正常工作。

除了检查nunit程序集外,我们还可以要求nunit api检查是否正在执行测试。

using NUnit.Framework;

// ...

if (TestContext.CurrentContext != null)
{
    // nunit test detected
    // Do some setup to avoid error
}

编辑:

请注意,如果需要,可以自动生成TestContext


2
请不要仅在此处转储代码。解释它的作用。
nkr

2

只需使用此:

AppDomain.CurrentDomain.IsDefaultAppDomain()

在测试模式下,它将返回false。


1

我很不高兴最近有这个问题。我以稍微不同的方式解决了它。首先,我不愿意假设nunit框架永远不会加载到测试环境之外。我特别担心开发人员在其机器上运行该应用程序。所以我走了调用栈。其次,我可以假设测试代码永远不会针对发行版二进制文件运行,因此我确保该代码在发行版系统中不存在。

internal abstract class TestModeDetector
{
    internal abstract bool RunningInUnitTest();

    internal static TestModeDetector GetInstance()
    {
    #if DEBUG
        return new DebugImplementation();
    #else
        return new ReleaseImplementation();
    #endif
    }

    private class ReleaseImplementation : TestModeDetector
    {
        internal override bool RunningInUnitTest()
        {
            return false;
        }
    }

    private class DebugImplementation : TestModeDetector
    {
        private Mode mode_;

        internal override bool RunningInUnitTest()
        {
            if (mode_ == Mode.Unknown)
            {
                mode_ = DetectMode();
            }

            return mode_ == Mode.Test;
        }

        private Mode DetectMode()
        {
            return HasUnitTestInStack(new StackTrace()) ? Mode.Test : Mode.Regular;
        }

        private static bool HasUnitTestInStack(StackTrace callStack)
        {
            return GetStackFrames(callStack).SelectMany(stackFrame => stackFrame.GetMethod().GetCustomAttributes(false)).Any(NunitAttribute);
        }

        private static IEnumerable<StackFrame> GetStackFrames(StackTrace callStack)
        {
            return callStack.GetFrames() ?? new StackFrame[0];
        }

        private static bool NunitAttribute(object attr)
        {
            var type = attr.GetType();
            if (type.FullName != null)
            {
                return type.FullName.StartsWith("nunit.framework", StringComparison.OrdinalIgnoreCase);
            }
            return false;
        }

        private enum Mode
        {
            Unknown,
            Test,
            Regular
        }

我发现在发布发行版本时测试调试版本的想法通常不是一个好主意。
Patrick M

1

奇迹般有效

if (AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName.ToLowerInvariant().StartsWith("nunit.framework")) != null)
{
    fileName = @"C:\Users\blabla\xxx.txt";
}
else
{
    var sfd = new SaveFileDialog
    {     ...     };
    var dialogResult = sfd.ShowDialog();
    if (dialogResult != DialogResult.OK)
        return;
    fileName = sfd.FileName;
}


1

单元测试将跳过应用程序入口点。至少对于wpf,winforms和控制台应用程序main()不会调用。

如果main方法的调用比我们在运行时调用的要多,否则我们处于单元测试模式下:

public static bool IsUnitTest { get; private set; } = true;

[STAThread]
public static void main()
{
    IsUnitTest = false;
    ...
}

0

考虑到您的代码通常在Windows窗体应用程序的主(gui)线程中运行,并且您希望它在测试中运行时表现不同,因此可以检查

if (SynchronizationContext.Current == null)
{
    // code running in a background thread or from within a unit test
    DoSomething();
}
else
{
    // code running in the main thread or any other thread where
    // a SynchronizationContext has been set with
    // SynchronizationContext.SetSynchronizationContext(synchronizationContext);
    DoSomethingAsync();
}

我将其用于fire and forgot在gui应用程序中想要的代码,但是在单元测试中,我可能需要计算结果进行断言,并且我不想弄乱正在运行的多个线程。

适用于MSTest。它的优点是我的代码不需要检查测试框架本身,并且如果我真的需要某个测试中的异步行为,则可以设置自己的SynchronizationContext。

请注意,这不是Determine if code is running as part of a unit testOP所要求的可靠方法,因为代码可以在线程内运行,但是对于某些情况,这可能是一个很好的解决方案(另:如果我已经从后台线程运行,则可能没有必要开始一个新的)。


0

在单元测试器下运行时,Application.Current为null。至少对于使用MS Unit测试器的WPF应用程序而言。如果需要,这是一个简单的测试。此外,在代码中使用Application.Current时要记住的一些事项。


0

我在代码中的VB中使用了以下内容,以检查我们是否参与单元测试。我特别不希望测试打开Word

    If Not Application.ProductName.ToLower().Contains("test") then
        ' Do something 
    End If

0

如何使用反射和类似的东西:

var underTest = Assembly.GetCallingAssembly()!= typeof(MainForm).Assembly;

调用程序集将是您的测试用例所在的位置,并且仅替换MainForm中正在测试的代码中的某种类型。


-3

测试课程时,还有一个非常简单的解决方案...

只需给您正在测试的类一个这样的属性即可:

// For testing purposes to avoid running certain code in unit tests.
public bool thisIsUnitTest { get; set; }

现在,您的单元测试可以将“ thisIsUnitTest”布尔值设置为true,因此在您要跳过的代码中添加:

   if (thisIsUnitTest)
   {
       return;
   } 

它比检查组件容易和快捷。让我想起了Ruby On Rails,您将在其中查看是否处于TEST环境中。


1
我认为您在这里不满意,因为您依赖测试本身来修改类的行为。
Riegardt Steyn 2014年

1
这并不比这里的所有其他答案差。
DonO
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.