web-dev-qa-db-fra.com

Déterminer si le code est en cours d'exécution dans le cadre d'un test unitaire

J'ai un test unitaire (nUnit). De nombreuses couches de la pile d'appels d'une méthode échoueront si elle s'exécute via un test unitaire.

Idéalement, vous utiliseriez quelque chose comme moqueur pour configurer l'objet dont dépend cette méthode, mais il s'agit d'un code tiers et je ne peux le faire sans beaucoup de travail.

Je ne veux pas de méthodes d'installation spécifiques à nUnit - il y a trop de niveaux ici et c'est une mauvaise façon de faire des tests unitaires.

Au lieu de cela, j'aimerais ajouter quelque chose comme ceci au plus profond de la pile d'appels 

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

Donc, des idées sur la façon d'écrire IsRunningInUnitTest?

P.S. Je suis pleinement conscient que ce n'est pas un bon design, mais je pense c'est mieux que les alternatives.

89
Ryan

Je l'ai déjà fait auparavant - je devais me tenir le nez pendant que je le faisais, mais je l'ai fait. Le pragmatisme bat le dogmatisme à chaque fois. Bien sûr, si il y a une manière agréable de le refactoriser pour l'éviter, ce serait génial.

En gros, j'avais une classe "UnitTestDetector" qui vérifiait si le framework NUnit Assembly était chargé dans l'AppDomain actuel. Il suffit de faire cela une fois, puis de mettre en cache le résultat. Moche, mais simple et efficace.

71
Jon Skeet

En prenant l’idée de Jon, c’est ce que j’ai trouvé -

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

Tuyautez à l'arrière, nous sommes tous assez grands pour reconnaître que nous faisons quelque chose que nous ne devrions probablement pas;)

66
Ryan

Adapté de la réponse de Ryan. Celui-ci est destiné au framework de test unitaire MS.

La raison pour laquelle j'ai besoin de cela est parce que je montre un MessageBox sur des erreurs. Mais mes tests unitaires testent également le code de traitement des erreurs et je ne souhaite pas qu'un MessageBox apparaisse lors de l'exécution des tests unitaires.

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

Et voici un test unitaire pour cela:

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

J'utilise une approche similaire à celle de Tallseth

C’est le code de base qui pourrait être facilement modifié pour inclure la mise en cache . Une autre bonne idée serait d’ajouter un séparateur à IsRunningInUnitTest et d’appeler UnitTestDetector.IsRunningInUnitTest = false au point d’entrée principal de votre projet pour éviter son exécution.

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;
        }
    }
}
17
Jürgen Steinblock

En simplifiant la solution de Ryan, vous pouvez simplement ajouter la propriété statique suivante à n'importe quelle classe:

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

Peut-être utile, vérifier le ProcessName actuel:

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

Et cette fonction devrait aussi être vérifiée par unittest:

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

Références:
Matthew Watson dans http://social.msdn.Microsoft.com/Forums/en-US/csharplanguage/thread/11e68468-c95e-4c43-b02b-7045a52b407e/

10
Kiquenet

En mode test, Assembly.GetEntryAssembly() semble être null.

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

Notez que si Assembly.GetEntryAssembly() est null, Assembly.GetExecutingAssembly() ne l’est pas.

La documentation dit: La méthode GetEntryAssembly peut renvoyer null quand un assembly géré a été chargé à partir d'une application non gérée.

9
Eric Bole-Feysot

Quelque part dans le projet testé:

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

Quelque part dans votre projet de test unitaire:

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

Élégant, non. Mais simple et rapide. AssemblyInitializer est pour MS Test. Je m'attendrais à ce que d'autres cadres de test aient des équivalents.

7
Edward Brey

J'utilise ceci only pour ignorer la logique qui désactive tous les TraceAppender dans log4net lors du démarrage lorsque aucun débogueur n'est connecté. Cela permet aux tests unitaires de se connecter à la fenêtre de résultats Resharper même en cas d'exécution en mode non-débogage.

La méthode qui utilise cette fonction est soit appelée au démarrage de l'application, soit au début d'un appareil de test.

Il est similaire au message de Ryan, mais utilise LINQ, supprime l'exigence System.Reflection, ne met pas en cache le résultat et est privé pour éviter toute utilisation (accidentelle).

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

J'étais malheureux d'avoir ce problème récemment. Je l'ai résolu d'une manière légèrement différente. Premièrement, je n’étais pas disposé à faire l’hypothèse que le cadre d’unités ne serait jamais chargé en dehors d’un environnement de test; J'étais particulièrement préoccupé par le fait que les développeurs exécutent l'application sur leurs machines. Alors j'ai marché la pile d'appels à la place. Deuxièmement, j'ai pu supposer que le code de test ne serait jamais exécuté contre les fichiers binaires de la version. Je me suis donc assuré que ce code n'existait pas dans un système de version.

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
        }
1
tallseth

fonctionne comme un charme

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
tom nobleman

J'ai utilisé les éléments suivants dans VB dans mon code pour vérifier si nous participons à un test unitaire. spécifiquement je ne voulais pas que le test ouvre Word

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

Avoir une référence au framework nunit ne signifie pas que le test est en cours. Par exemple, dans Unity, lorsque vous activez les tests de mode de lecture, les références à une unité sont ajoutées au projet. Et lorsque vous exécutez un jeu, les références sont existantes, ainsi UnitTestDetector ne fonctionnerait pas correctement.

Au lieu de vérifier l'assemblage des unités, nous pouvons demander à l'API des unités de vérifier si le code est en cours d'exécution ou non.

using NUnit.Framework;

// ...

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

Considérant que votre code est normalement exécuté dans le thread principal (gui) d’une application Windows Forms et que vous voulez qu’il se comporte différemment lors de l’exécution d’un test, vous pouvez le vérifier. 

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

J'utilise ceci pour le code que je veux fire and forgot dans une application graphique, mais dans les tests unitaires, j'ai peut-être besoin du résultat calculé pour une assertion et je ne veux pas gâcher plusieurs threads en cours d'exécution.

Fonctionne pour MSTest. L'avantage est que mon code n'a pas besoin de vérifier le framework de test lui-même et si j'ai vraiment besoin du comportement asynchrone dans un certain test, je peux définir mon propre SynchronizationContext.

Sachez que ce n'est pas une méthode fiable pour Determine if code is running as part of a unit test comme demandé par OP, car le code pourrait être exécuté à l'intérieur d'un thread, mais dans certains cas, cela pourrait être une bonne solution (aussi: si je suis déjà en cours d'exécution à partir d'un thread en arrière-plan, il se peut que ce ne soit pas le cas nécessaire pour en commencer un nouveau).

0

Application.Current est null lors de l'exécution sous le testeur d'unité. Au moins pour mon application WPF utilisant le testeur MS Unit. C'est un test facile à faire si nécessaire. Aussi, gardez à l’esprit lorsque vous utilisez Application.Current dans votre code.

0
Glaucus