Objednat jednotkové testy

Někdy můžete chtít mít testy jednotek spuštěné v určitém pořadí. V ideálním případě by na pořadí, ve kterém jednotkové testy běží, nemělo záležet, a je nejlepší praxe se vyhnout řazení jednotkových testů. Bez ohledu na to může být potřeba to udělat. V takovém případě tento článek ukazuje, jak uspořádat testovací běhy.

Poznámka:

Řazení testů a paralelizace testů jsou samostatné aspekty. Určení pořadí provádění určuje posloupnost, ve které se testy spustí, ale pokud je povolená paralelizace, může několik testů běžet souběžně. Chcete-li zaručit, že testy běží po jednom v zadaném pořadí, musíte také zakázat paralelizaci.

Pokud chcete zdrojový kód procházet, přečtěte si ukázkové úložiště order .NET Core unit tests.

Návod

Kromě možností řazení popsaných v tomto článku zvažte vytvoření vlastních seznamů skladeb s Visual Studio jako alternativu.

Pořadí podle abecedy

Poznámka:

MSTest spouští testy ve výchozím nastavení postupně v rámci třídy. Pokud konfigurujete paralelismus pomocí <Parallelize> nastavení v .runsettings souboru, testy napříč třídami se můžou spouštět souběžně a řazení ovlivní pouze sekvenci v rámci každé třídy.

MSTest vyhledá testy ve stejném pořadí, v jakém jsou definovány v testovací třídě.

Při spouštění prostřednictvím Průzkumníka testů (v Visual Studio nebo v Visual Studio Code) jsou testy seřazené v abecedním pořadí podle názvu testu.

Při spuštění mimo Průzkumníka testů se testy spustí v pořadí, v jakém jsou definovány v testovací třídě.

Poznámka:

Test s názvem Test14 se spustí před Test2, i když je číslo 2 menší než 14. Důvodem je to, že řazení názvů testů používá textový název testu.

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

Počínaje verzí MSTest 3.6 umožňuje nová možnost runsettings spouštět testy podle názvů testů v Průzkumníkech testů i na příkazovém řádku. Pokud chcete tuto funkci povolit, přidejte OrderTestsByNameInClass nastavení do souboru runsettings:

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

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

</RunSettings>

Pořadí náhodně

V MSTest 4.3 a novějších spusťte testy v náhodném pořadí, aby se mezi testy zobobnily skryté závislosti řazení. Pokud chcete povolit náhodné pořadí, nastavte RandomizeTestOrder v souboru runsettings hodnotu true :

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

  <MSTest>
    <RandomizeTestOrder>true</RandomizeTestOrder>
  </MSTest>

</RunSettings>

Pokud chcete, aby bylo náhodné pořadí reprodukovatelné, nastavte možnost RandomTestOrderSeed na celočíselnou počáteční hodnotu. MSTest pak při každém spuštění znovu přehraje stejné pořadí. Když semeno ponecháte nenastavené, MSTest pro každé spuštění vygeneruje nové semeno. Nekombinujte RandomizeTestOrder s OrderTestsByNameInClass, protože MSTest používá současně pouze jeden režim řazení. Další informace naleznete v tématu Konfigurace MSTest.

Testovací architektura xUnit umožňuje větší členitost a kontrolu pořadí spuštění testů. Implementujete rozhraní ITestCaseOrderer a ITestCollectionOrderer pro řízení pořadí testovacích případů pro třídu nebo testovací kolekce.

Poznámka:

xUnit spouští testovací třídy paralelně ve výchozím nastavení. Testy v rámci jedné třídy vždy běží postupně, takže ITestCaseOrderer řídí sekvenci v rámci této třídy. Chcete-li zakázat paralelismus napříč všemi třídami, použijte [assembly: CollectionBehavior(DisableTestParallelization = true)] na úrovni sestavení, například v AssemblyInfo.cs libovolném zdrojovém souboru v testovacím projektu.

Seřazení podle testovacího případu podle abecedy

Pokud chcete testovací případy objednat podle názvu metody, implementujete ITestCaseOrderer a zadáte mechanismus řazení.

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

Pak v testovací třídě nastavíte pořadí testovacího případu pomocí 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);
    }
}

Seřadit podle sbírek abecedně

Pokud chcete uspořádat testovací kolekce podle jejich zobrazovaného názvu, implementujete ITestCollectionOrderer a zadáte mechanismus řazení.

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

Vzhledem k tomu, že testovací kolekce potenciálně běží paralelně, je nutné explicitně zakázat paralelizaci testů kolekcí pomocí CollectionBehaviorAttribute. Pak určete implementaci pro 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;
    }
}

Pořadí podle vlastního atributu

Pokud chcete organizovat testy xUnit s vlastními atributy, musíte nejprve mít atribut, na který se můžete spolehnout. Definujte TestPriorityAttribute následující:

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

Dále zvažte následující PriorityOrderer implementaci ITestCaseOrderer rozhraní.

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

Pak v testovací třídě nastavíte pořadí testovacího případu s TestCaseOrdererAttribute na 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);
    }
}

Pořadí podle priority

Poznámka:

NUnit spouští testy postupně v rámci jednoho vlákna ve výchozím nastavení. Pokud jste nepoužili [Parallelizable] atributy, [Order] je samotný atribut dostatečný k zajištění sériového spuštění v zadané sekvenci.

NUnit poskytuje nástroj pro explicitní vyvolání testů OrderAttribute. Testy s tímto atributem jsou spuštěny před testy bez. Hodnota objednávky se používá k určení pořadí spuštění testů jednotek.

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

Další kroky