Udostępnij za pośrednictwem


Zamów testy jednostkowe

Czasami może być konieczne uruchomienie testów jednostkowych w określonej kolejności. W idealnym przypadku kolejność uruchamiania testów jednostkowych nie powinna mieć znaczenia i najlepszym rozwiązaniem jest unikanie porządkowania testów jednostkowych. Niezależnie od tego, może być konieczne zrobienie tego. W takim przypadku w tym artykule przedstawiono sposób zamawiania przebiegów testów.

Uwaga

Kolejność testów i równoległość testów są oddzielnymi problemami. Określenie kolejności wykonywania określa sekwencję uruchamiania testów, ale jeśli jest włączona równoległość, wiele testów może być nadal uruchamianych jednocześnie. Aby zagwarantować, że testy są uruchamiane pojedynczo w określonej kolejności, należy również wyłączyć równoległość.

Jeśli wolisz przeglądać kod źródłowy, sprawdź przykładowe repozytorium order .NET Core unit tests.

Wskazówka

Oprócz możliwości zamawiania opisanych w tym artykule rozważ tworzenie niestandardowych list odtwarzania za pomocą Visual Studio jako alternatywy.

Kolejność alfabetycznie

Uwaga

Narzędzie MSTest uruchamia testy sekwencyjnie w klasie domyślnie. Jeśli skonfigurujesz równoległość przy użyciu ustawienia w pliku, testy między klasami mogą być uruchamiane współbieżnie i kolejność ma wpływ tylko na sekwencję w każdej klasie.

Narzędzie MSTest odnajduje testy w tej samej kolejności, w której są zdefiniowane w klasie testowej.

W przypadku uruchamiania za pomocą Eksploratora testów (w Visual Studio lub w Visual Studio Code) testy są uporządkowane w kolejności alfabetycznej na podstawie ich nazwy testu.

W przypadku uruchamiania poza Eksploratorem testów testy są wykonywane w kolejności, w której są zdefiniowane w klasie testowej.

Uwaga

Test o nazwie zostanie uruchomiony wcześniej , mimo że liczba jest mniejsza niż . Dzieje się tak, ponieważ kolejność nazw testowych używa nazwy tekstowej 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);
    }
}

Począwszy od msTest 3.6, nowa opcja runsettings umożliwia uruchamianie testów według nazw testów zarówno w Eksploratorach testów, jak i w wierszu polecenia. Aby włączyć tę funkcję, dodaj ustawienie do pliku runsettings:

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

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

</RunSettings>

Struktura testów xUnit umożliwia bardziej szczegółowość i kontrolę kolejności przebiegów testów. Implementujesz interfejsy i w celu kontrolowania kolejności przypadków testowych dla klasy lub kolekcji testowych.

Uwaga

Narzędzie xUnit uruchamia klasy testowe równolegle domyślnie. Testy w jednej klasie zawsze są uruchamiane sekwencyjnie, dlatego steruje sekwencją w tej klasie. Aby wyłączyć równoległość we wszystkich klasach, zastosuj na poziomie zestawu, na przykład w pliku źródłowym lub w dowolnym pliku źródłowym w projekcie testowym.

Kolejność według przypadku testowego alfabetycznie

Aby uporządkować przypadki testowe według nazwy metody, należy zaimplementować mechanizm i udostępnić mechanizm porządkowania.

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

Następnie w klasie testowej ustawisz kolejność przypadków testowych przy użyciu klasy .

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

Kolejność według kolekcji alfabetycznie

Aby uporządkować kolekcje testowe według ich nazwy wyświetlanej , należy zaimplementować i udostępnić mechanizm porządkowania.

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

Ponieważ kolekcje testów mogą być uruchamiane równolegle, należy jawnie wyłączyć równoległość testów kolekcji za pomocą elementu . Następnie określ implementację na .

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

Kolejność według atrybutu niestandardowego

Aby zamówić testy xUnit z atrybutami niestandardowymi, najpierw musisz opierać się na atrybucie. Zdefiniuj element w następujący sposób:

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

Następnie rozważ następującą implementację interfejsu .

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

Następnie w klasie testowej ustawisz kolejność przypadków testowych od do .

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

Kolejność według priorytetu

Uwaga

NUnit uruchamia testy sekwencyjnie w jednym wątku domyślnie. Jeśli nie zastosowano atrybutów, sam atrybut jest wystarczający do zagwarantowania wykonywania szeregowego w określonej sekwencji.

Aby jawnie określić testy, NUnit udostępnia . Testy z tym atrybutem są uruchamiane przed testami bez. Wartość zamówienia służy do określania kolejności uruchamiania testów jednostkowych.

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

Dalsze kroki

Pokrycie kodu testu jednostkowego