Ich habe einen Unit-Test (nUnit). In vielen Schichten des Aufrufstapels schlägt eine Methode fehl, wenn sie über einen Komponententest ausgeführt wird.
Idealerweise würden Sie das Objekt, von dem diese Methode abhängt, mit einer Art Spott einrichten, aber dies ist Code von Drittanbietern, und ich kann das nicht ohne viel Arbeit tun.
Ich möchte keine Setup-nUnit-spezifischen Methoden verwenden. Es gibt zu viele Ebenen und es ist eine schlechte Methode, einen Unit-Test durchzuführen.
Stattdessen möchte ich so etwas tief im Call-Stack einfügen
#IF DEBUG // Unit tests only included in debug build
if (IsRunningInUnitTest)
{
// Do some setup to avoid error
}
#endif
Also irgendwelche Ideen, wie man IsRunningInUnitTest schreibt?
P.S. Ich bin mir völlig bewusst, dass dies kein großartiges Design ist, aber ich denke es ist besser als die Alternativen.
Ich habe das schon mal gemacht - ich musste meine Nase dabei halten, aber ich tat es. Pragmatismus schlägt jedes Mal den Dogmatismus. Wenn es gibt Ein schöner Weg, den Sie umgestalten können, um es zu vermeiden, wäre das natürlich großartig.
Im Grunde hatte ich eine "UnitTestDetector" -Klasse, die überprüft hat, ob die NUnit-Framework-Assembly in die aktuelle AppDomain geladen wurde. Das musste nur einmal gemacht werden, dann wurde das Ergebnis zwischengespeichert. Hässlich, aber einfach und effektiv.
Auf Jons Idee bin ich gekommen -
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; }
}
}
Pipe nach hinten, wir sind alle groß genug, um zu erkennen, wenn wir etwas tun, was wir wahrscheinlich nicht tun sollten;)
Angepasst von Ryans Antwort. Dieses ist für das MS-Unit-Test-Framework.
Der Grund, warum ich das brauche, ist, dass ich eine MessageBox auf Fehler zeige. Meine Unit-Tests testen jedoch auch den Fehlerbehandlungscode, und ich möchte nicht, dass eine MessageBox beim Ausführen von Unit-Tests angezeigt wird.
/// <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; }
}
Und hier ist ein Unit-Test dafür:
[TestMethod]
public void IsInUnitTest()
{
Assert.IsTrue(UnitTestDetector.IsInUnitTest,
"Should detect that we are running inside a unit test."); // lol
}
Ich verwende einen ähnlichen Ansatz als Tallseth
Dies ist der grundlegende Code, der leicht geändert werden kann, um das Zwischenspeichern einzuschließen. Eine andere gute Idee wäre, einen Setter zu IsRunningInUnitTest
hinzuzufügen und UnitTestDetector.IsRunningInUnitTest = false
am Haupteinstiegspunkt Ihres Projekts aufzurufen, um die Ausführung des Codes zu vermeiden.
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;
}
}
}
Um die Lösung von Ryan zu vereinfachen, können Sie jeder Klasse einfach die folgende statische Eigenschaft hinzufügen:
public static readonly bool IsRunningFromNUnit =
AppDomain.CurrentDomain.GetAssemblies().Any(
a => a.FullName.ToLowerInvariant().StartsWith("nunit.framework"));
Vielleicht nützlich, wenn Sie den aktuellen Prozessnamen überprüfen:
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
}
}
Und diese Funktion sollte auch von unittest geprüft werden:
[TestClass]
public class TestUnittestRunning
{
[TestMethod]
public void UnitTestRunningTest()
{
Assert.IsTrue(MyTools.UnitTestMode);
}
}
Verweise:
Matthew Watson in http://social.msdn.Microsoft.com/Forums/en-US/csharplanguage/thread/11e68468-c95e-4c43-b02b-7045a52b407d/
Im Testmodus scheint Assembly.GetEntryAssembly()
null
zu sein.
#IF DEBUG // Unit tests only included in debug build
if (Assembly.GetEntryAssembly() == null)
{
// Do some setup to avoid error
}
#endif
Beachten Sie, dass wenn Assembly.GetEntryAssembly()
null
ist, Assembly.GetExecutingAssembly()
nicht.
Die documentation sagt: Die GetEntryAssembly
-Methode kann null
zurückgeben, wenn eine verwaltete Assembly aus einer nicht verwalteten Anwendung geladen wurde.
Irgendwo im getesteten Projekt:
public static class Startup
{
public static bool IsRunningInUnitTest { get; set; }
}
Irgendwo in Ihrem Gerätetestprojekt:
[TestClass]
public static class AssemblyInitializer
{
[AssemblyInitialize]
public static void Initialize(TestContext context)
{
Startup.IsRunningInUnitTest = true;
}
}
Elegant, nein. Aber unkompliziert und schnell. AssemblyInitializer
ist für MS Test. Ich würde erwarten, dass andere Testframeworks Äquivalente haben.
Ich verwende diese only zum Überspringen der Logik, die alle TraceAppenders in log4net während des Startvorgangs deaktiviert, wenn kein Debugger angeschlossen ist. Auf diese Weise können sich Unit-Tests auch im Nicht-Debug-Modus im Resharper-Ergebnisfenster anmelden.
Die Methode, die diese Funktion verwendet, wird entweder beim Start der Anwendung oder beim Starten eines Testgeräts aufgerufen.
Es ist ähnlich wie Ryans Beitrag, verwendet jedoch LINQ, löscht die Anforderung von System.Reflection, speichert das Ergebnis nicht zwischen und ist privat, um (versehentlichen) Missbrauch zu verhindern.
private static bool IsNUnitRunning()
{
return AppDomain.CurrentDomain.GetAssemblies().Any(Assembly => Assembly.FullName.ToLowerInvariant().StartsWith("nunit.framework"));
}
Ich war kürzlich unzufrieden mit diesem Problem. Ich habe es etwas anders gelöst. Erstens wollte ich nicht davon ausgehen, dass das Nunit-Framework niemals außerhalb einer Testumgebung geladen wird. Ich war besonders besorgt über die Entwickler, die die App auf ihren Computern ausführen. Also ging ich stattdessen den Call Stack. Zweitens konnte ich davon ausgehen, dass Testcode niemals mit Binärdateien von Releases ausgeführt wird. Daher habe ich sichergestellt, dass dieser Code in einem Releasesystem nicht vorhanden ist.
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
}
klappt wunderbar
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;
}
.
Ich habe Folgendes in VB in meinem Code verwendet, um zu überprüfen, ob wir einen Komponententest durchführen. Ich wollte eigentlich nicht, dass der Test Word öffnet
If Not Application.ProductName.ToLower().Contains("test") then
' Do something
End If
Einen Verweis auf das Nunit-Framework zu haben, bedeutet nicht, dass der Test tatsächlich ausgeführt wird. Wenn Sie beispielsweise in Unity Tests für den Wiedergabemodus aktivieren, werden die Nunit-Referenzen zum Projekt hinzugefügt. Wenn Sie ein Spiel ausführen, sind die Verweise vorhanden, sodass UnitTestDetector nicht ordnungsgemäß funktioniert.
Anstatt auf nunit Assembly zu prüfen, können wir nun nunit api überprüfen, ob der Code gerade getestet wird oder nicht.
using NUnit.Framework;
// ...
if (TestContext.CurrentContext != null)
{
// nunit test detected
// Do some setup to avoid error
}
Wenn Sie bedenken, dass Ihr Code normalerweise im Haupt- (gui) -Thread einer Windows Forms-Anwendung ausgeführt wird, möchten Sie, dass er sich in einem Test, auf den Sie prüfen können, anders verhält
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();
}
Ich verwende dies für Code, den ich in einer GUI-Anwendung fire and forgot
möchte, aber in den Unit-Tests brauche ich möglicherweise das berechnete Ergebnis für eine Assertation, und ich möchte nicht, dass mehrere Threads laufen.
Funktioniert für MSTest. Der Vorteil ist, dass mein Code nicht nach dem Testframework selbst suchen muss, und wenn ich wirklich das asynchrone Verhalten in einem bestimmten Test brauche, kann ich meinen eigenen SynchronizationContext festlegen.
Beachten Sie, dass dies keine zuverlässige Methode für Determine if code is running as part of a unit test
ist, wie von OP angefordert, da Code in einem Thread ausgeführt werden könnte. In bestimmten Szenarien kann dies jedoch eine gute Lösung sein (auch: Wenn ich bereits einen Hintergrund-Thread ausführte, ist dies möglicherweise nicht der Fall.) notwendig, um ein neues zu beginnen).
Application.Current ist null, wenn unter dem Gerätetester ausgeführt wird. Zumindest für meine WPF-App mit MS Unit Tester. Dies ist ein einfacher Test, wenn nötig. Beachten Sie auch, wenn Sie Application.Current in Ihrem Code verwenden.