Condividi tramite


Ordinare i test unitari

In alcuni casi, potrebbe essere necessario eseguire gli unit test in un ordine specifico. Idealmente, l'ordine in cui vengono eseguiti gli unit test non dovrebbe essere importante ed è consigliabile evitare di ordinare gli unit test. Tuttavia, potrebbe essere necessario farlo. In tal caso, questo articolo illustra come ordinare le esecuzioni dei test.

Nota

L'ordinamento dei test e la parallelizzazione dei test sono problematiche separate. Se si specifica un ordine di esecuzione, viene determinata la sequenza in cui vengono avviati i test, ma se la parallelizzazione è abilitata, più test possono comunque essere eseguiti contemporaneamente. Per garantire che i test vengano eseguiti uno alla volta nell'ordine specificato, è necessario disabilitare anche la parallelizzazione.

Se si preferisce esplorare il codice sorgente, vedere il repository di esempio order .NET core unit test.

Suggerimento

Oltre alle funzionalità di ordinamento descritte in questo articolo, prendere in considerazione creare playlist personalizzate con Visual Studio come alternativa.

Ordinare alfabeticamente

Nota

MSTest esegue i test in sequenza all'interno di una classe per impostazione predefinita. Se si configura il parallelismo usando l'impostazione <Parallelize> in un .runsettings file, i test tra le classi possono essere eseguiti simultaneamente e l'ordinamento influisce solo sulla sequenza all'interno di ogni classe.

MSTest rileva i test nello stesso ordine in cui sono definiti nella classe di test.

Quando si esegue Esplora test (in Visual Studio o in Visual Studio Code), i test vengono ordinati in ordine alfabetico in base al nome del test.

Quando vengono eseguiti all'esterno di Esplora test, i test vengono eseguiti nell'ordine in cui sono definiti nella classe di test.

Nota

Un test denominato Test14 verrà eseguito prima di Test2 anche se il numero di 2 è minore di 14. Ciò è dovuto al fatto che l'ordinamento dei nomi dei test usa il nome del testo dei 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);
    }
}

A partire da MSTest 3.6, una nuova opzione runsettings consente di eseguire test in base ai nomi dei test sia in Esplora test che nella riga di comando. Per abilitare questa funzionalità, aggiungere l'impostazione OrderTestsByNameInClass al file runsettings:

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

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

</RunSettings>

Il framework di test xUnit consente una maggiore granularità e controllo dell'ordine di esecuzione dei test. Le interfacce ITestCaseOrderer e ITestCollectionOrderer vengono implementate per controllare l'ordine dei test case per una classe o le raccolte di test.

Nota

xUnit esegue classi di test in parallelo per impostazione predefinita. I test all'interno di una singola classe vengono sempre eseguiti in sequenza, quindi ITestCaseOrderer controlla la sequenza all'interno di tale classe. Per disabilitare il parallelismo in tutte le classi, applicare [assembly: CollectionBehavior(DisableTestParallelization = true)] a livello di assembly, ad esempio in AssemblyInfo.cs o in qualsiasi file di origine nel progetto di test.

Ordinare alfabeticamente in base al test case

Per ordinare i test case in base al nome del metodo, è necessario implementare ITestCaseOrderer e fornire un meccanismo di ordinamento.

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

Quindi, in una classe di test viene impostato l'ordine dei test case tramite l'opzione 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);
    }
}

Ordinare alfabeticamente in base alla raccolta

Per ordinare le raccolte di test in base al nome visualizzato, è necessario implementare ITestCollectionOrderer e fornire un meccanismo di ordinamento.

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

Poiché le raccolte di test possono essere eseguite in parallelo, è necessario disabilitare in modo esplicito la parallelizzazione dei delle raccolte tramite l’opzione CollectionBehaviorAttribute. Specificare quindi l'implementazione a 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;
    }
}

Ordinare in base all'attributo personalizzato

Per ordinare i test xUnit con attributi personalizzati, è necessario innanzitutto un attributo su cui basarsi. Definire un TestPriorityAttribute come segue:

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

Considerare quindi la seguente PriorityOrdererimplementazione dell'interfaccia 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());
}

Viene quindi impostato l'ordine del caso di test in una classe di test utilizzando TestCaseOrdererAttribute per 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);
    }
}

Ordinare in base alla priorità

Nota

NUnit esegue i test in sequenza all'interno di un singolo thread per impostazione predefinita. A meno che non siano stati applicati [Parallelizable] attributi, l'attributo [Order] da solo è sufficiente per garantire l'esecuzione seriale nella sequenza specificata.

Per ordinare i test in modo esplicito, NUnit fornisce un OrderAttribute. I test con questo attributo vengono avviati prima di quelli che non ne dispongono. Il valore dell'ordine viene usato per determinare l'ordine di esecuzione degli unit test.

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

Passaggi successivi