Stellen Sie fest, ob Code im Rahmen eines Komponententests ausgeführt wird


105

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 so etwas wie Verspotten verwenden, um das Objekt einzurichten, von dem diese Methode abhängt, aber dies ist Code von Drittanbietern, und das kann ich nicht ohne viel Arbeit tun.

Ich möchte keine nUnit-spezifischen Methoden einrichten - hier gibt es zu viele Ebenen und es ist eine schlechte Art, Unit-Tests durchzuführen.

Stattdessen möchte ich so etwas tief im Aufrufstapel hinzufü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?

PS Ich bin mir völlig bewusst, dass dies kein großartiges Design ist, aber ich denke, es ist besser als die Alternativen.


5
Sie sollten Code von Drittanbietern nicht direkt oder indirekt in einem Komponententest testen. Sie sollten Ihre zu testende Methode von der Implementierung durch Drittanbieter isolieren.
Craig Stuntz

14
Ja - das ist mir klar - in einer Ideenwelt, aber manchmal müssen wir ein bisschen pragmatisch sein, wenn es um Dinge geht, nein?
Ryan

9
Zurück zu Craigs Kommentar - ich bin mir nicht sicher, ob das stimmt. Wenn meine Methode davon abhängt, dass sich die Bibliothek eines Drittanbieters auf eine bestimmte Weise verhält, sollte dies dann nicht Teil des Tests sein? Wenn sich die App eines Drittanbieters ändert, soll mein Test fehlschlagen. Wenn Sie Ihre Tests mit der Funktionsweise der Drittanbieter-App verspotten, nicht mit der tatsächlichen Funktionsweise.
Ryan

2
Ryan, Sie können Annahmen über das Verhalten von Drittanbietern testen, aber das ist ein separater Test. Sie müssen Ihren eigenen Code isoliert testen.
Craig Stuntz

2
Ich verstehe, was Sie sagen, aber für alles andere als ein triviales Beispiel würden Sie über eine große (große) Menge an Arbeit sprechen, und es gibt nichts, was sicherstellen könnte, dass die Annahmen, die Sie in Ihrem Test überprüfen, mit Ihren Annahmen in Ihren tatsächlichen Methoden übereinstimmen . Hmm - Debatte für einen Blog-Beitrag Ich denke, ich werde dir eine E-Mail schreiben, wenn ich meine Gedanken zusammen habe.
Ryan

Antworten:


80

Ich habe das schon einmal gemacht - ich musste meine Nase halten, während ich es tat, aber ich tat es. Pragmatismus schlägt jedes Mal Dogmatismus. Natürlich, wenn es ist eine schöne Art und Weise Sie es zu vermeiden Refactoring kann, das wäre toll.

Grundsätzlich hatte ich eine "UnitTestDetector" -Klasse, die überprüfte, ob die NUnit-Framework-Assembly in die aktuelle AppDomain geladen wurde. Dies musste nur einmal durchgeführt und dann das Ergebnis zwischengespeichert werden. Hässlich, aber einfach und effektiv.


Gibt es ein Beispiel für UnitTestDetector? und ähnlich für MSTest?
Kiquenet

4
@Kiquenet: Ich denke, ich würde nur AppDomain.GetAssembliesdie relevante Assembly verwenden und nach ihr suchen - für MSTest müssten Sie sich ansehen, welche Assemblys geladen werden. Schauen Sie sich Ryans Antwort als Beispiel an.
Jon Skeet

Dies ist kein guter Ansatz für mich. Ich rufe eine UnitTest-Methode aus einer Konsolen-App auf und es wird angenommen, dass es sich um eine UnitTest-App handelt.
Bizhan

1
@Bizhan: Ich würde vorschlagen, dass Sie sich in einer ziemlich speziellen Situation befinden und nicht erwarten sollten, dass allgemeinere Antworten funktionieren. Möglicherweise möchten Sie eine neue Frage mit all Ihren spezifischen Anforderungen stellen. (Was ist der Unterschied zwischen "Ihrem Code, der von einer Konsolenanwendung aus aufgerufen wird" und "einem Testläufer"? Wie möchten Sie zwischen Ihrer Konsolenanwendung und einem anderen konsolenbasierten Testläufer unterscheiden?)
Jon Skeet

74

Als ich Jons Idee aufnahm, kam ich auf Folgendes:

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

Hinten sind wir alle groß genug, um zu erkennen, wenn wir etwas tun, was wir wahrscheinlich nicht tun sollten;)


2
+1 Gute Antwort. Dies kann jedoch erheblich
cdiggins

Das spezielle Projekt, für das ich dies geschrieben habe, war (und ist es immer noch!) .NET 2.0, also kein Linq.
Ryan

Diese Verwendung hat bei mir funktioniert, aber es scheint, dass sich der Name der Assembly seitdem geändert hat. Ich wechselte zu Kiquenets Lösung
The_Black_Smurf

Ich musste die Protokollierung für Travis Ci Builds deaktivieren, es hat alles
eingefroren

Funktioniert für mich, ich muss .NET Core 3-Fehler mit Rasiermesser hacken, die nur in Unit-Tests auftreten.
jjxtra

62

Angepasst an Ryans Antwort. Dieser ist für das MS Unit Test Framework.

Der Grund, warum ich das brauche, ist, dass ich bei Fehlern eine MessageBox anzeige. Meine Komponententests testen jedoch auch den Fehlerbehandlungscode, und ich möchte nicht, dass beim Ausführen von Komponententests eine MessageBox 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
    }

8
Ich habe einen besseren Weg, der Ihr MessageBox-Problem löst und diesen Hack ungültig macht und mehr Unit-Testfälle liefert. Ich verwende eine Klasse, die eine Schnittstelle implementiert, die ich ICommonDialogs nenne. Die Implementierungsklasse zeigt alle Popup-Dialoge an (MessageBox, Dateidialoge, Farbauswahl, Datenbankverbindungsdialog usw.). Klassen, die Meldungsfelder anzeigen müssen, akzeptieren ICommonDiaglogs als Konstruktorparameter, den wir dann im Komponententest verspotten können. Bonus: Sie können die erwarteten MessageBox-Anrufe bestätigen.
Tony O'Hagan

1
@ Tony, gute Idee. Das ist eindeutig der beste Weg, dies zu tun. Ich weiß nicht, was ich damals gedacht habe. Ich denke, die Abhängigkeitsinjektion war zu dieser Zeit noch neu für mich.
Dan-Gph

3
Im Ernst, Menschen lernen etwas über Abhängigkeitsinjektion und verspotten zweitens Objekte. Die Abhängigkeitsinjektion wird Ihre Programmierung revolutionieren.
Dan-Gph

2
Ich werde UnitTestDetector.IsInUnitTest als "return true" implementieren und Ihr Unit-Test wird bestanden. ;) Eines dieser lustigen Dinge, die unmöglich zu testen scheinen.
Samer Adra

1
Microsoft.VisualStudio.QualityTools.UnitTestFramework hat bei mir nicht mehr funktioniert. Es wurde in Microsoft.VisualStudio.TestPlatform.TestFramework geändert - das funktioniert wieder.
Alexander

21

Um Ryans Lösung 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"));

2
Ziemlich genau wie die Antwort von dan-gph (obwohl das nach dem VS-Toolset suchte, nicht nach nunit).
Ryan

18

Ich benutze einen ähnlichen Ansatz wie Tallseth

Dies ist der Basiscode, der leicht geändert werden kann, um das Caching einzuschließen. Eine weitere gute Idee wäre, einen Setter zum Haupteinstiegspunkt Ihres Projekts hinzuzufügen IsRunningInUnitTestund diesen aufzurufen UnitTestDetector.IsRunningInUnitTest = false, um die Codeausführung 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;
        }
    }
}

Ich mag diesen Ansatz mehr als die höher bewerteten Antworten. Ich glaube nicht, dass man davon ausgehen kann, dass Unit-Test-Assemblys immer nur während eines Unit-Tests geladen werden und der Prozessname auch von Entwickler zu Entwickler variieren kann (z. B. verwenden einige den R # -Test-Runner).
EM0

Dieser Ansatz würde funktionieren, sucht jedoch jedes Mal nach diesen Attributen, wenn der Getter IsRunningInUnitTest aufgerufen wird. In einigen Fällen kann dies die Leistung beeinträchtigen. Das Überprüfen des AssemblyName ist billiger, da es nur einmal durchgeführt wird. Die Idee mit dem öffentlichen Setter ist gut, aber in diesem Fall sollte die UnitTestDetector- Klasse in einer gemeinsam genutzten Assembly platziert werden.
Sergey

13

Möglicherweise 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 überprüft werden:

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

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


|| processName.StartsWith("testhost") // testhost.x86für VS 2019
Kiquenet

9

Im Testmodus Assembly.GetEntryAssembly()scheint zu sein null.

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

Beachten Sie, dass Assembly.GetEntryAssembly()ist null,Assembly.GetExecutingAssembly() ist es nicht.

In der Dokumentation heißt es: Die GetEntryAssemblyMethode kann zurückkehren, nullwenn eine verwaltete Assembly aus einer nicht verwalteten Anwendung geladen wurde.


8

Irgendwo im getesteten Projekt:

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

Irgendwo in Ihrem Unit-Test-Projekt:

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

Elegant, nein. Aber unkompliziert und schnell. AssemblyInitializerist für MS Test. Ich würde erwarten, dass andere Test-Frameworks Äquivalente haben.


1
Wenn der Code, den Sie testen, zusätzliche AppDomains erstellt, IsRunningInUnitTestwird dieser in diesen AppDomains nicht auf true gesetzt.
Edward Brey

Es kann jedoch leicht gelöst werden, indem eine gemeinsam genutzte Assembly hinzugefügt oder IsRunningInUnitTest in jeder Domäne deklariert wird .
Sergey

3

Ich benutze das nur zum Überspringen von Logik, die alle TraceAppender in log4net während des Startvorgangs deaktiviert, wenn kein Debugger angeschlossen ist. Auf diese Weise können Komponententests auch im Nicht-Debug-Modus im Resharper-Ergebnisfenster protokolliert werden.

Die Methode, die diese Funktion verwendet, wird entweder beim Start der Anwendung oder beim Starten eines Testgeräts aufgerufen.

Es ähnelt Ryans Post, verwendet jedoch LINQ, löscht die System.Reflection-Anforderung, speichert das Ergebnis nicht im Cache und ist privat, um (versehentlichen) Missbrauch zu verhindern.

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

3

Ein Verweis auf das Nunit-Framework bedeutet nicht, dass der Test tatsächlich ausgeführt wird. Wenn Sie beispielsweise in Unity Tests für den Wiedergabemodus aktivieren, werden dem Projekt die Nunit-Referenzen hinzugefügt. Und wenn Sie ein Spiel ausführen, sind die Referenzen vorhanden, sodass UnitTestDetector nicht richtig funktioniert.

Anstatt nach der Versammlung von nunit zu suchen, können wir nuni api bitten, zu überprüfen, ob der Code gerade ausgeführt wird oder nicht.

using NUnit.Framework;

// ...

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

Bearbeiten:

Beachten Sie, dass der TestContext bei Bedarf automatisch generiert wird.


2
Bitte geben Sie hier nicht einfach den Code ein. Erklären Sie, was es tut.
nkr

2

Verwenden Sie einfach dies:

AppDomain.CurrentDomain.IsDefaultAppDomain()

Im Testmodus wird false zurückgegeben.


1

Ich war unglücklich, dieses Problem kürzlich zu haben. 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 Entwickler, die die App auf ihren Computern ausführen. Also ging ich stattdessen den Anrufstapel entlang. Zweitens konnte ich davon ausgehen, dass Testcode niemals für Release-Binärdateien ausgeführt werden würde, und stellte daher sicher, dass dieser Code in einem Release-System 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
        }

Ich finde die Idee, die Debug-Version während des Versands der Release-Version zu testen, im Allgemeinen eine schlechte Idee.
Patrick M

1

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

.


1

Unit-Tests überspringen den Einstiegspunkt der Anwendung. Zumindest für wpf werden Winforms und Konsolenanwendungen main()nicht aufgerufen.

Wenn die Hauptmethode aufgerufen wird, befinden wir uns zur Laufzeit , andernfalls befinden wir uns im Unit-Test- Modus:

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

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

0

Wenn Sie bedenken, dass Ihr Code normalerweise im Hauptthread (GUI) einer Windows Forms-Anwendung ausgeführt wird und Sie möchten, dass er sich während der Ausführung eines Tests, 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 fire and forgotin einer GUI-Anwendung verwenden möchte, aber in den Komponententests benötige ich möglicherweise das berechnete Ergebnis für eine Bestätigung und möchte nicht mit mehreren laufenden Threads herumspielen.

Funktioniert für MSTest. Der Vorteil ist, dass mein Code nicht nach dem Testframework selbst suchen muss und wenn ich das asynchrone Verhalten in einem bestimmten Test wirklich benötige, kann ich meinen eigenen SynchronizationContext festlegen.

Beachten Sie, dass dies keine zuverlässige Methode ist, Determine if code is running as part of a unit testwie von OP angefordert, da Code möglicherweise in einem Thread ausgeführt wird. In bestimmten Szenarien kann dies jedoch eine gute Lösung sein (auch: Wenn ich bereits von einem Hintergrund-Thread ausgeführt werde, ist dies möglicherweise nicht erforderlich einen neuen starten).


0

Application.Current ist null, wenn es unter dem Unit-Tester ausgeführt wird. Zumindest für meine WPF-App mit MS Unit Tester. Dies ist bei Bedarf ein einfacher Test. Beachten Sie auch Folgendes, wenn Sie Application.Current in Ihrem Code verwenden.


0

Ich habe Folgendes in VB in meinem Code verwendet, um zu überprüfen, ob wir in einem Komponententest sind. Insbesondere wollte ich nicht, dass der Test Word öffnet

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

0

Wie wäre es mit Reflexion und so etwas:

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

In der aufrufenden Assembly befinden sich Ihre Testfälle und ersetzen nur MainForm durch einen Typ, der in Ihrem zu testenden Code enthalten ist.


-3

Es gibt auch eine wirklich einfache Lösung, wenn Sie eine Klasse testen ...

Geben Sie einfach der Klasse, in der Sie eine Eigenschaft testen, Folgendes an:

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

Jetzt kann Ihr Komponententest den Booleschen Wert "thisIsUnitTest" auf "true" setzen. Fügen Sie also in dem Code, den Sie überspringen möchten, Folgendes hinzu:

   if (thisIsUnitTest)
   {
       return;
   } 

Es ist einfacher und schneller als die Inspektion der Baugruppen. Erinnert mich an Ruby On Rails, wo Sie nachsehen würden, ob Sie sich in der TEST-Umgebung befinden.


1
Ich denke, Sie wurden hier abgelehnt, weil Sie sich auf den Test selbst verlassen, um das Verhalten der Klasse zu ändern.
Riegardt Steyn

1
Dies ist nicht schlimmer als alle anderen Antworten hier.
DonO
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.