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

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

理想情况下,你可以使用类似 Mocking 的东西来设置这个方法所依赖的对象,但是这是第三方代码,我不能不做很多工作。

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

相反,我想要做的是在调用堆栈的深处添加类似这样的内容

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

那么,对于如何编写 IsRunningInUnitTest 有什么想法吗?

附注: 我完全知道这不是一个伟大的设计,但我 好好想想它比其他选择更好。

66104 次浏览

我以前也这么做过,我必须捏着鼻子,但我做到了。实用主义每次都能打败教条主义。当然,如果有一个很好的方式 ,您可以重构,以避免它,那将是巨大的。

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

接受乔恩的主意,这是我想出来的

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; }
}
}

安静点,我们都是大孩子了,当我们在做一些我们可能不该做的事情的时候,我们都能认出来;)

改编自 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
}

我使用这个 只有跳过逻辑,该逻辑在没有附加调试器的情况下在启动期间禁用 log4net 中的所有 TraceAppender。这允许单元测试记录到 Resharper 结果窗口,即使在非调试模式下运行。

使用此函数的方法要么在应用程序启动时调用,要么在开始测试装备时调用。

它类似于瑞安的帖子,但使用 LINQ,删除系统。反射要求,不缓存结果,并且是私有的,以防止(意外)误用。

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

我最近对这个问题很不高兴。我用稍微不同的方法解决了这个问题。首先,我不愿意假设 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
}

在测试模式下,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()不是。

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

也许有用,检查当前的 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/》中写道

当您测试一个类时,也有一个非常简单的解决方案..。

简单地给你正在测试的类一个像下面这样的属性:

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

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

   if (thisIsUnitTest)
{
return;
}

它比检查程序集更容易和更快。让我想起了 RubyOnRails,在那里您可以查看是否处于 TEST 环境中。

我和 Tallseth 使用类似的方法

这是基本的代码,可以很容易地修改,以包括缓存。 另一个好主意是向 IsRunningInUnitTest添加一个 setter,并将 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;
}
}
}

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

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

正在测试的项目中的某个地方:

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 测试的。我希望其他测试框架也有类似的。

非常有效

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;
}

.

考虑到您的代码通常在 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的可靠方法,因为代码可以在线程内运行,但对于某些情况,这可能是一个很好的解决方案(还有: 如果我已经在一个后台线程中运行,可能不需要启动一个新的线程)。

引用 nunit 框架并不意味着测试实际上正在运行。例如,在 Unity 中,当您激活播放模式测试时,nunit 引用被添加到项目中。当您运行一个游戏时,引用是存在的,所以 UnitTestDetector 不能正常工作。

不需要检查 nunit 汇编,我们可以要求 nunit api 检查是否正在执行 test 中的代码。

using NUnit.Framework;


// ...


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

编辑:

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

申请。在单元测试器下运行时,Current 为空。至少对我的 WPF 应用程序使用微软单元测试。如果需要的话,这是个很简单的测试。另外,在使用 Application 时要记住的一些事情。代码中的当前内容。

在我的代码中,我在 VB 中使用了下面的代码来检查我们是否在单元测试中。特别是我不想让测试打开 Word

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

用这个:

AppDomain.CurrentDomain.IsDefaultAppDomain()

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

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

如果 main 方法被调用,那么我们在 运行时间中,否则我们在 单元测试模式中:

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


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

使用反射和类似这样的东西怎么样:

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

调用程序集将位于测试用例所在的位置,只需替换正在测试的代码中的某种 MainForm 类型。

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

我在类的顶部添加了一个名为 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));
            if (string.IsNullOrEmpty(System.Web.Hosting.HostingEnvironment.MapPath("~")))
{
// Running not as a web app (unit tests)
}


// Running as a web app