web-dev-qa-db-fra.com

Contrôle de l'ordre d'exécution des tests unitaires dans Visual Studio

D'accord, j'ai fini de chercher de bonnes informations à ce sujet. J'ai une série de tests unitaires qui appellent une classe statique qui, une fois initialisée, définit des propriétés qui ne peuvent pas (ou que je ne souhaite pas) modifier.

Mon problème est que je ne peux pas imposer un ordre défini pour l'exécution des tests. Si je le pouvais, je pourrais les exécuter de manière à ce que les propriétés statiques soient définies de manière fiable, et je pourrais les affirmer, mais malheureusement, le cadre Microsoft.VisualStudio.TestTools.UnitTesting les exécute simplement dans un ordre apparemment aléatoire. .

Donc, j'ai trouvé ceci http://msdn.Microsoft.com/en-us/library/Microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx qui dit dans la section Remarques "Cet attribut n'est pas utilisé par le système de test. Il est fourni à l'utilisateur à des fins personnalisées. " Hein? A quoi bon alors? S'attendent-ils à ce que je rédige mon propre wrapper de test pour tirer parti de ce fabuleux attribut (dont je pourrais facilement m'écrire si je voulais aller à ce niveau d'effort ...)

Donc, assez du coup de gueule; En bout de ligne, y a-t-il un moyen de contrôler l'ordre d'exécution de mes tests unitaires?

[TestMethod]
[Priority(0)]

etc. ne semble pas fonctionner, ce qui est logique, puisque Microsoft dit que ce ne sera pas le cas.

Aussi, s'il vous plaît pas de commentaires sur "violer l'isolement". TestClass isole ce que je teste, pas les TestMethods individuels. Quoi qu’il en soit, chaque test peut être exécuté indépendamment, il ne peut tout simplement pas être exécuté ensemble dans un ordre aléatoire, car il n’ya aucun moyen de supprimer la classe statique.

Oh, je connais aussi le "Test ordonné".

61
iGanja

Fusionner vos tests en un test géant fonctionnera. Pour rendre la méthode de test plus lisible, vous pouvez faire quelque chose comme:

[TestMethod]
public void MyIntegratonTestLikeUnitTest()
{
    AssertScenarioA();

    AssertScenarioB();

    ....
}

private void AssertScenarioA()
{
     // Assert
}

private void AssertScenarioB()
{
     // Assert
}

En fait, le problème que vous avez suggéré suggère que vous devriez probablement améliorer la testabilité de la mise en œuvre.

48
Gang Gao

Vous pouvez utiliser Playlist

Faites un clic droit sur la méthode de test -> Ajouter à la playlist -> Nouvelle playlist

l'ordre d'exécution sera celui que vous ajouterez à la liste de lecture, mais si vous voulez le modifier, vous avez le fichier

enter image description here

108
HB MAAM

Je ne vois personne mentionner la méthode d'attribut ClassInitialize. Les attributs sont assez simples.

Créez des méthodes marquées avec l'attribut [ClassInitialize()] ou [TestInitialize()] pour préparer les aspects de l'environnement dans lequel votre test unitaire sera exécuté. Le but de cela est d'établir un état connu pour l'exécution de votre test unitaire. Par exemple, vous pouvez utiliser la méthode [ClassInitialize()] ou [TestInitialize()] pour copier, modifier ou créer certains fichiers de données que votre test utilisera.

Créez des méthodes marquées de l'attribut [ClassCleanup()] ou [TestCleanUp{}] Pour rétablir l'état connu de l'environnement après l'exécution d'un test. Cela peut signifier la suppression de fichiers dans des dossiers ou le retour d'une base de données à un état connu. Un exemple de cela consiste à réinitialiser une base de données d'inventaire à un état initial après avoir testé une méthode utilisée dans une application de saisie de commande.

  • [ClassInitialize()] Utilisez ClassInitialize pour exécuter le code avant le premier test de la classe.

  • [ClassCleanUp()] Utilisez ClassCleanup pour exécuter le code une fois que tous les tests d'une classe ont été exécutés.

  • [TestInitialize()] Utilisez TestInitialize pour exécuter le code avant chaque test.

  • [TestCleanUp()] Utilisez TestCleanup pour exécuter le code après chaque test.

9
MattyMerrix

Comme l'ont déjà souligné les commentateurs, le fait que des tests reposent sur d'autres tests laisse présager d'un défaut de conception. Néanmoins, il existe des moyens pour y parvenir. Comme répondu à une question précédemment posée ici, vous pouvez créer des tests unitaires commandés, qui sont essentiellement un seul conteneur de test garantissant la séquence de test.

Voici un guide sur MSDN: http://msdn.Microsoft.com/en-us/library/ms182631.aspx

6
Honza Brestan

Puisque vous avez déjà mentionné la fonctionnalité de test commandé fournie par la structure de test de Visual Studio, je l'ignorerai. Vous semblez également être conscient du fait que ce que vous essayez d'accomplir pour tester cette classe statique est une "mauvaise idée", alors je l'ignorerai.

Voyons plutôt comment vous pouvez réellement garantir que vos tests sont exécutés dans l’ordre que vous souhaitez. Une option (fournie par @gaog) est "une méthode de test, plusieurs fonctions de test", appelant vos fonctions de test dans l'ordre que vous souhaitez au sein d'une seule fonction marquée avec l'attribut TestMethod. C’est le moyen le plus simple et le seul inconvénient est que la première fonction de test à échouer empêchera l’une des fonctions de test restantes de s’exécuter.

Avec votre description de la situation, voici la solution que je vous suggère d’utiliser.

Si la partie en gras vous pose problème, vous pouvez exécuter une exécution ordonnée de tests isolés en exploitant la fonctionnalité de test pilotée par les données intégrée. C'est plus compliqué et c'est un peu sale, mais le travail est fait.

En bref, vous définissez une source de données (telle qu'un fichier CSV ou une table de base de données) qui contrôle l'ordre dans lequel vous devez exécuter vos tests et les noms des fonctions qui contiennent réellement la fonctionnalité de test. Vous accrochez ensuite cette source de données à un test piloté par les données, utilisez l'option de lecture séquentielle et exécutez vos fonctions, dans l'ordre de votre choix, en tant que tests individuels.

[TestClass]
public class OrderedTests
{
    public TestContext TestContext { get; set; }

    private const string _OrderedTestFilename = "TestList.csv";

    [TestMethod]
    [DeploymentItem(_OrderedTestFilename)]
    [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", _OrderedTestFilename, _OrderedTestFilename, DataAccessMethod.Sequential)]
    public void OrderedTests()
    {
        var methodName = (string)TestContext.DataRow[0];
        var method = GetType().GetMethod(methodName);
        method.Invoke(this, new object[] { });
    }

    public void Method_01()
    {
        Assert.IsTrue(true);
    }

    public void Method_02()
    {
        Assert.IsTrue(false);
    }

    public void Method_03()
    {
        Assert.IsTrue(true);
    }
}

Dans mon exemple, j'ai un fichier de support appelé TestList.csv, qui est copié dans la sortie. Cela ressemble à ceci:

TestName
Method_01
Method_02
Method_03

Vos tests seront exécutés dans l’ordre que vous avez spécifié et dans l’isolation de test normale (c’est-à-dire si l’un échoue, le reste est toujours exécuté, mais en partageant des classes statiques).

Ce qui précède n’est vraiment que l’idée de base. Si je l’utilisais en production, je générerais les noms des fonctions de test et leur ordre de manière dynamique avant l’exécution du test. Peut-être en exploitant PriorityAttribute, vous avez trouvé un code de réflexion simple pour extraire les méthodes de test de la classe et les ordonner de manière appropriée, puis écrivez cet ordre dans la source de données.

5
Todd Bowles

Comme vous devez le savoir maintenant, les puristes disent qu'il est interdit d'exécuter des tests ordonnés. Cela pourrait être vrai pour les tests unitaires. MSTest et d'autres frameworks de tests unitaires sont utilisés pour exécuter des tests unitaires purs, mais également des tests d'interface utilisateur, des tests d'intégration complète, etc. Peut-être que nous ne devrions pas les appeler cadres de tests unitaires, ou peut-être devrions-nous les utiliser en fonction de nos besoins. C'est ce que la plupart des gens font de toute façon.

J'exécute VS2015 et je DOIS exécuter des tests dans un ordre donné, car je lance des tests d'interface utilisateur (Selenium).

Priorité - Ne fait rien du tout Cet attribut n'est pas utilisé par le système de test. Il est fourni à l'utilisateur à des fins personnalisées. .

Ordertest - Cela fonctionne mais je ne le recommande pas car:

  1. Un Ordertest un fichier texte qui répertorie vos tests dans l'ordre dans lequel ils doivent être exécutés. Si vous modifiez un nom de méthode, vous devez corriger le fichier.
  2. L'ordre d'exécution du test est respecté à l'intérieur d'une classe. Vous ne pouvez pas commander quelle classe exécute ses tests en premier.
  3. Un fichier Ordertest est lié à une configuration, soit Debug, soit Release.
  4. Vous pouvez avoir plusieurs fichiers Ordertest, mais une méthode ne peut pas être répétée dans différents fichiers Ordertest. Vous ne pouvez donc pas avoir un fichier Ordertest pour Debug et un autre pour Release.

D'autres suggestions dans ce fil sont intéressantes, mais vous perdez la possibilité de suivre la progression du test sur Test Explorer.

Il ne vous reste plus que la solution que purist déconseillera, mais c’est la solution qui fonctionne: triez par ordre de déclaration .

L'exécuteur MSTest utilise un interop qui parvient à obtenir l'ordre de déclaration et cette astuce fonctionnera jusqu'à ce que Microsoft modifie le code de l'exécuteur de test.

Cela signifie que la méthode de test déclarée en premier lieu est exécutée avant celle déclarée en deuxième place, etc.

Pour vous simplifier la vie, l'ordre des déclarations doit correspondre à l'ordre alphabétique affiché dans l'Explorateur de tests.

  • A010_FirstTest
  • A020_SecondTest
  • etc
  • A100_TenthTest

Je suggère fortement des règles anciennes et éprouvées:

  • utilisez une étape de 10 car vous devrez insérer une méthode de test ultérieurement
  • évitez de renuméroter vos tests en utilisant une étape généreuse entre les numéros de test
  • utilisez 3 chiffres pour numéroter vos tests si vous exécutez plus de 10 tests
  • utilisez 4 chiffres pour numéroter vos tests si vous exécutez plus de 100 tests

TRES IMPORTANT

Afin d'exécuter les tests selon l'ordre de déclaration, vous devez utiliser Exécuter tout dans l'explorateur de tests.

Disons que vous avez 3 classes de test (dans mon cas, les tests pour Chrome, Firefox et Edge). Si vous sélectionnez une classe donnée et cliquez avec le bouton droit de la souris , exécutez les tests sélectionnés , il commence généralement par exécuter la méthode déclarée à la dernière place.

Encore une fois, comme je l’ai déjà dit, ordre déclaré et ordre indiqué devrait correspondre, sinon vous aurez de gros problèmes en un rien de temps.

4

Voici une classe qui peut être utilisée pour configurer et exécuter des tests ordonnés indépendamment du framework MS Ordered Tests, quelle que soit la raison - vous n'avez pas à ajuster les arguments de mstest.exe sur une machine de construction, ni à mélanger des ordres ordonnés avec des éléments non ordonnés dans une classe.

La structure de test d'origine ne considère que la liste des tests ordonnés comme un test unique. Ainsi, tout init/nettoyage tel que [TestInitalize ()] Init () n'est appelé qu'avant et après l'ensemble.

tilisation:

        [TestMethod] // place only on the list--not the individuals
        public void OrderedStepsTest()
        {
            OrderedTest.Run(TestContext, new List<OrderedTest>
            {
                new OrderedTest ( T10_Reset_Database, false ),
                new OrderedTest ( T20_LoginUser1, false ),
                new OrderedTest ( T30_DoLoginUser1Task1, true ), // continue on failure
                new OrderedTest ( T40_DoLoginUser1Task2, true ), // continue on failure
                // ...
            });                
        }

mise en œuvre:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace UnitTests.Utility
{    
    /// <summary>
    /// Define and Run a list of ordered tests. 
    /// 2016/08/25: Posted to SO by crokusek 
    /// </summary>    
    public class OrderedTest
    {
        /// <summary>Test Method to run</summary>
        public Action TestMethod { get; private set; }

        /// <summary>Flag indicating whether testing should continue with the next test if the current one fails</summary>
        public bool ContinueOnFailure { get; private set; }

        /// <summary>Any Exception thrown by the test</summary>
        public Exception ExceptionResult;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="testMethod"></param>
        /// <param name="continueOnFailure">True to continue with the next test if this test fails</param>
        public OrderedTest(Action testMethod, bool continueOnFailure = false)
        {
            TestMethod = testMethod;
            ContinueOnFailure = continueOnFailure;
        }

        /// <summary>
        /// Run the test saving any exception within ExceptionResult
        /// Throw to the caller only if ContinueOnFailure == false
        /// </summary>
        /// <param name="testContextOpt"></param>
        public void Run()
        {
            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                ExceptionResult = ex;
                throw;
            }
        }

        /// <summary>
        /// Run a list of OrderedTest's
        /// </summary>
        static public void Run(TestContext testContext, List<OrderedTest> tests)
        {
            Stopwatch overallStopWatch = new Stopwatch();
            overallStopWatch.Start();

            List<Exception> exceptions = new List<Exception>();

            int testsAttempted = 0;
            for (int i = 0; i < tests.Count; i++)
            {
                OrderedTest test = tests[i];

                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Start();

                testContext.WriteLine("Starting ordered test step ({0} of {1}) '{2}' at {3}...\n",
                    i + 1,
                    tests.Count,
                    test.TestMethod.Method,
                    DateTime.Now.ToString("G"));

                try
                {
                    testsAttempted++;
                    test.Run();
                }
                catch
                {
                    if (!test.ContinueOnFailure)
                        break;
                }
                finally
                {
                    Exception testEx = test.ExceptionResult;

                    if (testEx != null)  // capture any "continue on fail" exception
                        exceptions.Add(testEx);

                    testContext.WriteLine("\n{0} ordered test step {1} of {2} '{3}' in {4} at {5}{6}\n",
                        testEx != null ? "Error:  Failed" : "Successfully completed",
                        i + 1,
                        tests.Count,
                        test.TestMethod.Method,
                        stopWatch.ElapsedMilliseconds > 1000
                            ? (stopWatch.ElapsedMilliseconds * .001) + "s"
                            : stopWatch.ElapsedMilliseconds + "ms",
                        DateTime.Now.ToString("G"),
                        testEx != null
                            ? "\nException:  " + testEx.Message +
                                "\nStackTrace:  " + testEx.StackTrace +
                                "\nContinueOnFailure:  " + test.ContinueOnFailure
                            : "");
                }
            }

            testContext.WriteLine("Completed running {0} of {1} ordered tests with a total of {2} error(s) at {3} in {4}",
                testsAttempted,
                tests.Count,
                exceptions.Count,
                DateTime.Now.ToString("G"),
                overallStopWatch.ElapsedMilliseconds > 1000
                    ? (overallStopWatch.ElapsedMilliseconds * .001) + "s"
                    : overallStopWatch.ElapsedMilliseconds + "ms");

            if (exceptions.Any())
            {
                // Test Explorer prints better msgs with this hierarchy rather than using 1 AggregateException().
                throw new Exception(String.Join("; ", exceptions.Select(e => e.Message), new AggregateException(exceptions)));
            }
        }
    }
}
4
crokusek

Je ne parlerai pas de l'ordre des tests, désolé. D'autres l'ont déjà fait. De plus, si vous connaissez les "tests ordonnés" - eh bien, voici la réponse de MS VS au problème. Je sais que ces tests ordonnés ne sont pas amusants. Mais ils pensaient que ce serait "ça" et il n'y a vraiment rien de plus dans MSTest à ce sujet.

J'écris à propos d'une de vos hypothèses:

comme il n'y a aucun moyen de démolir la classe statique.

Sauf si votre classe statique représente un état externe à l'ensemble du processus externe à votre code (comme par exemple l'état d'un fichier natif non géré DLL la bibliothèque est P/Invoquée par le reste de votre code), votre hypothèse que there is no way ce n'est pas vrai.

Si votre classe statique fait référence à cela, alors désolée, vous avez parfaitement raison, le reste de cette réponse est sans importance. Néanmoins, comme vous ne l'avez pas dit, je suppose que votre code est "géré".

Pensez et vérifiez la chose AppDomain. Rarement, cela est nécessaire, mais c'est exactement le cas lorsque vous voudrez probablement les utiliser.

Vous pouvez créer un nouveau domaine d'application, instancier le test à cet emplacement et exécuter la méthode de test à cet endroit. Les données statiques utilisées par le code géré y seront isolées et une fois l’opération terminée, vous pourrez décharger l’AppDomain et toutes les données, y compris la statique, s’évaporent. Ensuite, le test suivant initialisera un autre domaine, et ainsi de suite.

Cela fonctionnera sauf si vous avez un état externe que vous devez suivre. AppDomains n'isole que la mémoire gérée. Tout fichier natif DLL sera toujours chargé par processus et leur état sera partagé par tous les AppDomains.

En outre, la création/suppression des domaines d'applications ralentira les tests. En outre, vous pouvez avoir des problèmes avec la résolution de l'Assemblée dans l'application enfant, mais ils peuvent être résolus avec une quantité raisonnable de code réutilisable.

En outre, vous pouvez avoir de petits problèmes avec la transmission des données de test à l'enfant AppDomain et vice-versa. Les objets passés devront soit être sérialisables, soit MarshalByRef ou etc. Parler interdomaine, c'est presque comme IPC.

Cependant, faites attention ici, ce sera géré à 100%. Si vous prenez des précautions supplémentaires et ajoutez un peu de travail à la configuration d'AppDomain, vous pourrez même transmettre des délégués et les exécuter dans le domaine cible. Ensuite, au lieu de faire une configuration poilue entre domaines, vous pouvez envelopper vos tests avec quelque chose comme:

void testmethod()
{
    TestAppDomainHelper.Run( () =>
    {
        // your test code
    });
}

ou même

[IsolatedAppDomain]
void testmethod()
{
    // your test code
}

si votre infrastructure de test prend en charge la création de tels wrappers/extensions. Après quelques recherches et travaux initiaux, leur utilisation est presque triviale.

2
quetzalcoatl

Je vois que ce sujet a presque 6 ans et nous avons maintenant une nouvelle version de Visual studio, mais je vous répondrai quand même. J'ai eu ce problème d'ordre dans Visual Studio 19 et je l'ai résolu en ajoutant une lettre majuscule (vous pouvez également ajouter une petite lettre) devant le nom de votre méthode et par ordre alphabétique, comme ceci:

[TestMethod]
        public void AName1()
        {}
[TestMethod]
        public void BName2()
        {}

Etc. Je sais que cela ne semble pas attrayant, mais il semblerait que Visual trie vos tests dans Test Explorer par ordre alphabétique, peu importe la façon dont vous l'écrivez dans votre code. La playlist n'a pas fonctionné pour moi dans ce cas.

J'espère que cela aidera.

0
Taverna Joe