Partager via


Organiser les tests unitaires

Parfois, vous souhaiterez peut-être exécuter des tests unitaires dans un ordre spécifique. Dans l’idéal, l’ordre dans lequel les tests unitaires sont exécutés ne doit pas importer, et il est recommandé d’éviter de les classer. Quoi qu’il en soit, il peut être nécessaire de le faire. Dans ce cas, cet article montre comment organiser les tests.

Remarque

L’ordre des tests et la parallélisation des tests sont des problèmes distincts. La spécification d’un ordre d’exécution détermine la séquence dans laquelle les tests démarrent, mais si la parallélisation est activée, plusieurs tests peuvent toujours s’exécuter simultanément. Pour garantir que les tests s’exécutent un par un dans l’ordre spécifié, vous devez également désactiver la parallélisation.

Si vous préférez parcourir le code source, consultez l’exemple de référentiel order .NET Core unit tests.

Conseil

En plus des fonctionnalités de classement décrites dans cet article, envisagez de création de playlists personnalisées avec Visual Studio comme alternative.

Classement dans l’ordre alphabétique

Remarque

MSTest exécute des tests séquentiellement dans une classe par défaut. Si vous configurez le parallélisme à l’aide du <Parallelize> paramètre dans un .runsettings fichier, les tests entre les classes peuvent s’exécuter simultanément et l’ordre affecte uniquement la séquence dans chaque classe.

MSTest découvre les tests dans l'ordre dans lequel ils sont définis dans la classe de test.

Lors de l’exécution via l’Explorateur de tests (dans Visual Studio ou dans Visual Studio Code), les tests sont classés par ordre alphabétique en fonction de leur nom de test.

En dehors de l'Explorateur de tests, les tests sont exécutés dans l'ordre dans lequel ils sont définis dans la classe de test.

Remarque

Un test nommé Test14 s’exécutera avant Test2 même si le nombre 2 est inférieur à 14. Cela est dû au fait que le classement des noms de test utilise le nom textuel du test.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MSTest.Project;

[TestClass]
public class ByAlphabeticalOrder
{
    public static bool Test1Called;
    public static bool Test2Called;
    public static bool Test3Called;

    [TestMethod]
    public void Test2()
    {
        Test2Called = true;

        Assert.IsTrue(Test1Called);
        Assert.IsFalse(Test3Called);
    }

    [TestMethod]
    public void Test1()
    {
        Test1Called = true;

        Assert.IsFalse(Test2Called);
        Assert.IsFalse(Test3Called);
    }

    [TestMethod]
    public void Test3()
    {
        Test3Called = true;

        Assert.IsTrue(Test1Called);
        Assert.IsTrue(Test2Called);
    }
}

À compter de MSTest 3.6, une nouvelle option runsettings vous permet d’exécuter des tests par noms de test à la fois dans Les Explorateurs de tests et sur la ligne de commande. Pour activer cette fonctionnalité, ajoutez le OrderTestsByNameInClass paramètre à votre fichier runsettings :

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>

  <MSTest>
    <OrderTestsByNameInClass>true</OrderTestsByNameInClass>
  </MSTest>

</RunSettings>

Le framework de test xUnit offre une granularité et un contrôle accrus de l’ordre d’exécution des tests. Vous implémentez le ITestCaseOrderer et les interfaces ITestCollectionOrderer pour contrôler l’ordre des cas de test pour une classe ou des collections de tests.

Remarque

xUnit exécute des classes de test en parallèle par défaut. Les tests d’une seule classe s’exécutent toujours de manière séquentielle, ce qui ITestCaseOrderer contrôle la séquence dans cette classe. Pour désactiver le parallélisme entre toutes les classes, appliquez-le [assembly: CollectionBehavior(DisableTestParallelization = true)] au niveau de l’assembly, par exemple dans AssemblyInfo.cs ou dans n’importe quel fichier source de votre projet de test.

Classement des cas de test par ordre alphabétique

Pour classer des cas de test par leur nom de méthode, vous implémentez le ITestCaseOrderer et fournissez un mécanisme de classement.

using Xunit.Abstractions;
using Xunit.Sdk;

namespace XUnit.Project.Orderers;

public class AlphabeticalOrderer : ITestCaseOrderer
{
    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(
        IEnumerable<TTestCase> testCases) where TTestCase : ITestCase =>
        testCases.OrderBy(testCase => testCase.TestMethod.Method.Name);
}

Ensuite, dans une classe de test, vous définissez l’ordre des cas de test avec le TestCaseOrdererAttribute.

using Xunit;

namespace XUnit.Project;

[TestCaseOrderer(
    ordererTypeName: "XUnit.Project.Orderers.AlphabeticalOrderer",
    ordererAssemblyName: "XUnit.Project")]
public class ByAlphabeticalOrder
{
    public static bool Test1Called;
    public static bool Test2Called;
    public static bool Test3Called;

    [Fact]
    public void Test1()
    {
        Test1Called = true;

        Assert.False(Test2Called);
        Assert.False(Test3Called);
    }

    [Fact]
    public void Test2()
    {
        Test2Called = true;

        Assert.True(Test1Called);
        Assert.False(Test3Called);
    }

    [Fact]
    public void Test3()
    {
        Test3Called = true;

        Assert.True(Test1Called);
        Assert.True(Test2Called);
    }
}

Classement des collections par ordre alphabétique

Pour classer des collections de tests par leur nom d’affichage, vous implémentez le ITestCollectionOrderer et fournissez un mécanisme de classement.

using Xunit;
using Xunit.Abstractions;

namespace XUnit.Project.Orderers;

public class DisplayNameOrderer : ITestCollectionOrderer
{
    public IEnumerable<ITestCollection> OrderTestCollections(
        IEnumerable<ITestCollection> testCollections) =>
        testCollections.OrderBy(collection => collection.DisplayName);
}

Étant donné que des collections de tests peuvent être exécutées en parallèle, vous devez désactiver explicitement la parallélisation de test des collections avec le CollectionBehaviorAttribute. Spécifiez ensuite l’implémentation au TestCollectionOrdererAttribute.

using Xunit;

// Need to turn off test parallelization so we can validate the run order
[assembly: CollectionBehavior(DisableTestParallelization = true)]
[assembly: TestCollectionOrderer(
    ordererTypeName: "XUnit.Project.Orderers.DisplayNameOrderer",
    ordererAssemblyName: "XUnit.Project")]

namespace XUnit.Project;

[Collection("Xzy Test Collection")]
public class TestsInCollection1
{
    public static bool Collection1Run;

    [Fact]
    public static void Test()
    {
        Assert.True(TestsInCollection2.Collection2Run);     // Abc
        Assert.True(TestsInCollection3.Collection3Run);     // Mno
        Assert.False(TestsInCollection1.Collection1Run);    // Xyz

        Collection1Run = true;
    }
}

[Collection("Abc Test Collection")]
public class TestsInCollection2
{
    public static bool Collection2Run;

    [Fact]
    public static void Test()
    {
        Assert.False(TestsInCollection2.Collection2Run);    // Abc
        Assert.False(TestsInCollection3.Collection3Run);    // Mno
        Assert.False(TestsInCollection1.Collection1Run);    // Xyz

        Collection2Run = true;
    }
}

[Collection("Mno Test Collection")]
public class TestsInCollection3
{
    public static bool Collection3Run;

    [Fact]
    public static void Test()
    {
        Assert.True(TestsInCollection2.Collection2Run);     // Abc
        Assert.False(TestsInCollection3.Collection3Run);    // Mno
        Assert.False(TestsInCollection1.Collection1Run);    // Xyz

        Collection3Run = true;
    }
}

Classer par attribut personnalisé

Pour ordonner des tests xUnit avec des attributs personnalisés, vous avez d’abord besoin d’un attribut à utiliser. Définissez un TestPriorityAttribute comme suit :

namespace XUnit.Project.Attributes;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TestPriorityAttribute : Attribute
{
    public int Priority { get; private set; }

    public TestPriorityAttribute(int priority) => Priority = priority;
}

Envisagez ensuite l’implémentation PriorityOrderer suivante de l’interface ITestCaseOrderer.

using Xunit.Abstractions;
using Xunit.Sdk;
using XUnit.Project.Attributes;

namespace XUnit.Project.Orderers;

public class PriorityOrderer : ITestCaseOrderer
{
    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(
        IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
    {
        string assemblyName = typeof(TestPriorityAttribute).AssemblyQualifiedName!;
        var sortedMethods = new SortedDictionary<int, List<TTestCase>>();
        foreach (TTestCase testCase in testCases)
        {
            int priority = testCase.TestMethod.Method
                .GetCustomAttributes(assemblyName)
                .FirstOrDefault()
                ?.GetNamedArgument<int>(nameof(TestPriorityAttribute.Priority)) ?? 0;

            GetOrCreate(sortedMethods, priority).Add(testCase);
        }

        foreach (TTestCase testCase in
            sortedMethods.Keys.SelectMany(
                priority => sortedMethods[priority].OrderBy(
                    testCase => testCase.TestMethod.Method.Name)))
        {
            yield return testCase;
        }
    }

    private static TValue GetOrCreate<TKey, TValue>(
        IDictionary<TKey, TValue> dictionary, TKey key)
        where TKey : struct
        where TValue : new() =>
        dictionary.TryGetValue(key, out TValue? result)
            ? result
            : (dictionary[key] = new TValue());
}

Ensuite, dans une classe de test, vous définissez l’ordre des cas de test avec le TestCaseOrdererAttribute sur le PriorityOrderer.

using Xunit;
using XUnit.Project.Attributes;

namespace XUnit.Project;

[TestCaseOrderer(
    ordererTypeName: "XUnit.Project.Orderers.PriorityOrderer",
    ordererAssemblyName: "XUnit.Project")]
public class ByPriorityOrder
{
    public static bool Test1Called;
    public static bool Test2ACalled;
    public static bool Test2BCalled;
    public static bool Test3Called;

    [Fact, TestPriority(5)]
    public void Test3()
    {
        Test3Called = true;

        Assert.True(Test1Called);
        Assert.True(Test2ACalled);
        Assert.True(Test2BCalled);
    }

    [Fact, TestPriority(0)]
    public void Test2B()
    {
        Test2BCalled = true;

        Assert.True(Test1Called);
        Assert.True(Test2ACalled);
        Assert.False(Test3Called);
    }

    [Fact]
    public void Test2A()
    {
        Test2ACalled = true;

        Assert.True(Test1Called);
        Assert.False(Test2BCalled);
        Assert.False(Test3Called);
    }

    [Fact, TestPriority(-5)]
    public void Test1()
    {
        Test1Called = true;

        Assert.False(Test2ACalled);
        Assert.False(Test2BCalled);
        Assert.False(Test3Called);
    }
}

Classer par priorité

Remarque

NUnit exécute des tests séquentiellement dans un thread unique par défaut. Sauf si vous avez appliqué l'attribut [Parallelizable], l’attribut [Order] seul est suffisant pour garantir l’exécution en sérialisation dans la séquence spécifiée.

Pour classer des tests explicitement, NUnit fournit un OrderAttribute. Les tests avec cet attribut sont démarrés avant les tests sans. La valeur de l’ordre est utilisée pour déterminer l’ordre d’exécution des tests unitaires.

using NUnit.Framework;

namespace NUnit.Project;

public class ByOrder
{
    public static bool Test1Called;
    public static bool Test2ACalled;
    public static bool Test2BCalled;
    public static bool Test3Called;

    [Test, Order(5)]
    public void Test1()
    {
        Test1Called = true;

        Assert.That(Test2ACalled, Is.False);
        Assert.That(Test2BCalled, Is.True);
        Assert.That(Test3Called, Is.True);
    }

    [Test, Order(0)]
    public void Test2B()
    {
        Test2BCalled = true;

        Assert.That(Test1Called, Is.False);
        Assert.That(Test2ACalled, Is.False);
        Assert.That(Test3Called, Is.True);
    }

    [Test]
    public void Test2A()
    {
        Test2ACalled = true;

        Assert.That(Test1Called, Is.True);
        Assert.That(Test2BCalled, Is.True);
        Assert.That(Test3Called, Is.True);
    }

    [Test, Order(-5)]
    public void Test3()
    {
        Test3Called = true;

        Assert.That(Test1Called, Is.False);
        Assert.That(Test2ACalled, Is.False);
        Assert.That(Test2BCalled, Is.False);
    }
}

Prochaines étapes