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

时间:2010-07-02 16:52:19

标签: c# reflection nunit

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

理想情况下,你会使用像mocking这样的东西来设置这个方法所依赖的对象,但这是第三方代码,如果没有大量工作我就不能这样做。

我不想设置nUnit特定的方法 - 这里有太多级别,而且它是一种不好的单元测试方法。

相反,我想做的是在调用堆栈中添加类似这样的东西

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

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

P.S。我完全清楚这不是很好的设计,但我认为比其他选择更好。

20 个答案:

答案 0 :(得分:71)

我以前做过这个 - 我做的时候不得不捏住鼻子,但我做到了。实用主义每次都击败教条主义。当然,如果 是一种很好的方式,你可以重构以避免它,那就太好了。

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

答案 1 :(得分:68)

采取Jon的想法,这就是我想出来的 -

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 :(得分:52)

改编自Ryan的回答。这个用于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
    }

答案 3 :(得分:17)

我使用与tallseth类似的方法

这是可以轻松修改以包含缓存的基本代码。 另一个好主意是将一个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;
        }
    }
}

答案 4 :(得分:13)

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

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

答案 5 :(得分:10)

也许有用,检查当前的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);
    }
}

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

答案 6 :(得分: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()null,则Assembly.GetExecutingAssembly()不是。{/ p>

documentation说:当从非托管应用程序加载托管程序集时,GetEntryAssembly方法可以返回null

答案 7 :(得分: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 Test。我希望其他测试框架具有等价物。

答案 8 :(得分:3)

我只使用 来跳过逻辑,在没有连接调试器的情况下,在启动期间禁用log4net中的所有TraceAppender。这样即使在非调试模式下运行,单元测试也会记录到Resharper结果窗口。

使用此功能的方法可以在启动应用程序时启动,也可以在启动测试夹具时调用。

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

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

答案 9 :(得分: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
        }

答案 10 :(得分: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;
}

答案 11 :(得分:1)

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

如果调用main方法比在运行时中调用,否则在单元测试模式下:

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

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

答案 12 :(得分: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();
}

我在gui应用程序中使用这个代码我想fire and forgot但是在单元测试中我可能需要断言的计算结果而且我不想弄乱运行的多个线程

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

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

答案 13 :(得分:0)

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

我们可以要求nunit api检查是否正在执行测试代码,而不是检查nunit程序集。

using NUnit.Framework;

// ...

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

答案 14 :(得分:0)

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

答案 15 :(得分:0)

我在我的代码中的VB中使用了以下内容来检查我们是否在单元测试中。尤其是我不想测试打开Word

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

答案 16 :(得分:0)

只需使用此:

AppDomain.CurrentDomain.IsDefaultAppDomain()

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

答案 17 :(得分:0)

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

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

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

答案 18 :(得分:0)

我有一个更接近原始海报想要的解决方案。问题是如何设置测试标志以指示代码正在作为测试的一部分执行。可以用两行代码来实现。

我在类的顶部添加了一个名为RunningNunitTest的内部变量。确保将其设置为内部变量而不是公共变量。在构建项目时,我们不想导出此变量。同样,这就是我们将允许NUnit将其设置为true的方式。

NUnit无法访问我们代码中的私有变量或方法。这是一个简单的解决方法。在using语句和名称空间之间添加 [assembly:InternalsVisibleTo(“ NUnitTest”))装饰。这允许NUint访问任何内部变量或方法。我的NUnit测试项目名为“ NUintTest”。将此名称替换为您的NUint测试项目的名称。

就是这样!在您的NUnit测试中将RunningNunitTest设置为true。

using NetworkDeviceScanner;

[assembly: InternalsVisibleTo("NUnitTest")] // Add this decoration to your class

namespace NetworkDeviceScannerLibrary
{
    public class DetectDevice
    {
        internal bool RunningNunitTest = false; // Add this variable to your class

        public ulong TotalAddressesFound;
        public ulong ScanCount;

NUnit代码

var startIp = IPAddress.Parse("191.168.1.1");
var endIp = IPAddress.Parse("192.168.1.128");
var detectDevice = new DetectDevice
{
    RunningNunitTest = true
};
Assert.Throws<ArgumentOutOfRangeException>(() => detectDevice.DetectIpRange(startIp, endIp, null));

答案 19 :(得分:-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环境中。