Životní cyklus MSTest

MSTest poskytuje dobře definovaný životní cyklus pro testovací třídy a testovací metody, což umožňuje provádět operace nastavení a odstranění v různých fázích provádění testů. Pochopení životního cyklu pomáhá psát efektivní testy a vyhnout se běžným nástrahám.

Přehled životního cyklu

Životní cyklus seskupí do čtyř fází, které se provádějí od nejvyšší úrovně (sestavení) po nejnižší úroveň (testovací metoda):

  1. Úroveň sestavení: Spustí se jednou, když testovací sestavení načte a uvolní zatížení.
  2. Úroveň třídy: Spustí se jednou pro každou testovací třídu.
  3. Globální úroveň testu: Spustí se před a po každé testovací metodě v sestavení.
  4. Úroveň testu: Spustí se pro každou testovací metodu (včetně každého datového řádku v parametrizovaných testech).

Životní cyklus na úrovni sestavení

Metody životního cyklu sestavení se spustí jednou, když testovací sestavení načte a uvolní zatížení. Použijte je pro nákladné jednorázové nastavení, jako je inicializace databáze nebo spuštění služby.

AssemblyInitialize a AssemblyCleanup

[TestClass]
public class AssemblyLifecycleExample
{
    private static IHost? _host;

    [AssemblyInitialize]
    public static async Task AssemblyInit(TestContext context)
    {
        // Runs once before any tests in the assembly
        _host = await StartTestServerAsync();
        context.WriteLine("Test server started");
    }

    [AssemblyCleanup]
    public static async Task AssemblyCleanup(TestContext context)
    {
        // Runs once after all tests complete
        // TestContext parameter available in MSTest 3.8+
        if (_host != null)
        {
            await _host.StopAsync();
        }
    }

    private static Task<IHost> StartTestServerAsync()
    {
        // Server initialization
        return Task.FromResult<IHost>(null!);
    }
}

Požadavky

  • Metody musí být public static
  • Návratový typ: void, Tasknebo ValueTask (MSTest v3.3+)
  • AssemblyInitializevyžaduje jeden parametr.TestContext
  • AssemblyCleanup přijímá nulové parametry nebo jeden TestContext parametr (MSTest 3.8+)
  • Pro každé sestavení je povolený pouze jeden atribut.
  • Musí být ve třídě označené [TestClass]

Návod

Související analyzátory:

  • MSTEST0012 – ověří AssemblyInitialize podpis.
  • MSTEST0013 – ověří AssemblyCleanup podpis.

Sdílené montážní přípravky s AssemblyFixtureProvider

Poznámka:

Tato AssemblyFixtureProviderAttribute verze byla zavedena v MSTest 4.3.0.

Ve výchozím nastavení musí být metody [AssemblyInitialize] a [AssemblyCleanup] deklarovány v testovacím sestavení, které je používá. AssemblyFixtureProviderAttribute umožňuje deklarovat tyto metody jedinkrát ve sdílené knihovně a zajistit, aby byly rozpoznány a spuštěny jednou pro každé testovací sestavení, které ji používá. Použijte atribut na úrovni sestavení a nasměrujte ho na typ, který hostuje [AssemblyInitialize]/[AssemblyCleanup] metody.

// In a shared library.
public static class SharedAssemblyFixtures
{
    [AssemblyInitialize]
    public static void Init(TestContext context)
    {
        // Runs once per consuming test assembly.
    }

    [AssemblyCleanup]
    public static void Cleanup()
    {
    }
}
// In each test assembly that should run the shared fixtures.
[assembly: AssemblyFixtureProvider(typeof(SharedAssemblyFixtures))]

Atribut umožňuje v rámci sestavení více poskytovatelů, takže můžete sestavovat fixture z několika sdílených typů.

Životní cyklus na úrovni třídy

Metody životního cyklu třídy se spouštějí jednou pro každou třídu testu před a po všech testovacích metodách v této třídě. Použijte je k nastavení sdílené napříč testy ve třídě.

ClassInitialize a ClassCleanup

[TestClass]
public class ClassLifecycleExample
{
    private static HttpClient? _client;

    [ClassInitialize]
    public static void ClassInit(TestContext context)
    {
        // Runs once before any tests in this class
        _client = new HttpClient
        {
            BaseAddress = new Uri("https://api.example.com")
        };
    }

    [ClassCleanup]
    public static void ClassCleanup()
    {
        // Runs after all tests in this class complete
        _client?.Dispose();
    }

    [TestMethod]
    public async Task GetUsers_ReturnsSuccess()
    {
        HttpResponseMessage response = await _client!.GetAsync("/users");
        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}

Požadavky

  • Metody musí být public static
  • Návratový typ: void, Tasknebo ValueTask (MSTest v3.3+)
  • ClassInitializevyžaduje jeden parametr.TestContext
  • ClassCleanup přijímá nulové parametry nebo jeden TestContext parametr (MSTest 3.8+)
  • Pro každou třídu je povolený pouze jeden atribut.

Poznámka:

Od verze MSTest 4.2 hlásí MSTest selhání v ClassCleanup a AssemblyCleanup jako samostatné výsledky testů. Oddělený výsledek usnadňuje identifikaci selhání při čištění v Test Exploreru a sestavách TRX.

Chování dědičnosti

Určete, zda ClassInitialize se spouští pro odvozené třídy pomocí InheritanceBehavior:

[TestClass]
public class BaseTestClass
{
    [ClassInitialize(InheritanceBehavior.BeforeEachDerivedClass)]
    public static void BaseClassInit(TestContext context)
    {
        // Runs before each derived class's tests
    }
}

[TestClass]
public class DerivedTestClass : BaseTestClass
{
    [TestMethod]
    public void DerivedTest()
    {
        // BaseClassInit runs before this class's tests
    }
}
Dědičnost chování Description
None (výchozí) Inicializace se spustí pouze pro deklarující třídu.
BeforeEachDerivedClass Inicializace se provede před každou odvozenou třídou.

Návod

Související analyzátory:

  • MSTEST0010 – ověří ClassInitialize podpis.
  • MSTEST0011 – ověří ClassCleanup podpis.
  • MSTEST0034 - doporučuje používat ClassCleanupBehavior.EndOfClass.

Globální životní cyklus na úrovni testu

Poznámka:

Atributy globálního životního cyklu testů byly zavedeny v MSTest 3.10.0.

Globální metody životního cyklu testů běží před a po každé testovací metodě v celém sestavení, aniž by bylo nutné do každé testovací třídy přidávat kód.

GlobalTestInitialize a GlobalTestCleanup

[TestClass]
public class GlobalTestLifecycleExample
{
    [GlobalTestInitialize]
    public static void GlobalTestInit(TestContext context)
    {
        // Runs before every test method in the assembly
        context.WriteLine($"Starting test: {context.TestName}");
    }

    [GlobalTestCleanup]
    public static void GlobalTestCleanup(TestContext context)
    {
        // Runs after every test method in the assembly
        context.WriteLine($"Finished test: {context.TestName}");
    }
}

Požadavky

  • Metody musí být public static
  • Návratový typ: void, Tasknebo ValueTask
  • Musí mít přesně jeden parametr.TestContext
  • Musí být ve třídě označené [TestClass]
  • V sestavení je povoleno více metod s těmito atributy.

Poznámka:

Pokud existuje více metod GlobalTestInitialize nebo GlobalTestCleanup, není zaručeno pořadí jejich provádění. U TimeoutAttribute metod se nepodporuje GlobalTestInitialize .

Návod

Související analyzátor: MSTEST0050 – ověřuje globální metody zařízení testů.

Životní cyklus testu

Životní cyklus testovací úrovně běží pro každou testovací metodu. U parametrizovaných testů se životní cyklus spouští pro každý řádek dat.

Fáze nastavení

Použijte TestInitialize nebo konstruktor pro nastavení pro každý test:

[TestClass]
public class TestLevelSetupExample
{
    private Calculator? _calculator;

    public TestLevelSetupExample()
    {
        // Constructor runs before TestInitialize
        // Use for simple synchronous initialization
    }

    [TestInitialize]
    public async Task TestInit()
    {
        // Runs before each test method
        // Supports async, attributes like Timeout
        _calculator = new Calculator();
        await _calculator.InitializeAsync();
    }

    [TestMethod]
    public void Add_TwoNumbers_ReturnsSum()
    {
        int result = _calculator!.Add(2, 3);
        Assert.AreEqual(5, result);
    }
}

Konstruktor vs. TestInitialize:

Aspekt Konstruktor TestInitialize
Podpora asynchronních operací Ne Ano
Podpora časového limitu Ne Ano (s atributem [Timeout] )
Pořadí provádění First Po konstruktoru
Dědičnost Základ pak odvozený Základ pak odvozený
Chování výjimky Vyčištění a odstranění se nespustí (neexistuje žádná instance) Vyčištění a ukládání stále probíhají

Návod

Jaký přístup mám použít? Konstruktory jsou obecně upřednostňované, protože umožňují používat readonly pole, která vynucují neměnnost a usnadňují zdůvodnění třídy testů. Použijte TestInitialize , když potřebujete podporu asynchronní inicializace nebo vypršení časového limitu.

Můžete také kombinovat oba přístupy: použít konstruktor pro jednoduchou synchronní inicializaci readonly polí a TestInitialize pro další asynchronní nastavení, které závisí na těchto polích.

Volitelně můžete povolit analyzátory kódu, aby vynutily konzistentní přístup:

  • MSTEST0019 – Preferujte metody TestInitialize před konstruktory
  • MSTEST0020 – Preferujte konstruktory před metodami TestInitialize

Fáze provádění

Testovací metoda se spustí po dokončení instalace. U async testovacích metod nástroj MSTest očekává vrácenou hodnotu Task nebo ValueTask.

Výstraha

Asynchronní testovací metody nemají SynchronizationContext ve výchozím nastavení. To se nevztahuje na UITestMethod testy v UPW a WinUI, které běží ve vlákně uživatelského rozhraní.

Fáze vyčištění

Použijte TestCleanup nebo IDisposable/IAsyncDisposable pro vyčištění po každém testu:

[TestClass]
public class TestLevelCleanupExample
{
    private HttpClient? _client;

    [TestInitialize]
    public void TestInit()
    {
        _client = new HttpClient();
    }

    [TestCleanup]
    public void TestCleanup()
    {
        if (_client != null)
        {
            _client.Dispose();
        }
    }

    [TestMethod]
    public async Task GetData_ReturnsSuccess()
    {
        HttpResponseMessage response = await _client!.GetAsync("https://example.com");
        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}

Pořadí provádění vyčištění (odvozeno ze základu):

  1. TestCleanup (odvozená třída)
  2. TestCleanup (základní třída)
  3. DisposeAsync (pokud je implementováno)
  4. Dispose (pokud je implementováno)

Návod

Volitelně můžete povolit analyzátory kódu, aby vynutily konzistentní přístup k vyčištění:

  • MSTEST0021 – Preferujte Dispose před metodami TestCleanup
  • MSTEST0022 – Preferujte testCleanup před metodami Dispose

Pokud máte povolené jiné analyzátory než MSTest, například .NET pravidla analýzy kódu, může se zobrazit CA1001, který navrhuje, abyste implementovali vzor pro uvolňování, pokud testovací třída vlastní uvolnitelné prostředky. Toto chování je očekávané a měli byste postupovat podle pokynů analyzátoru.

Dokončení pořadí v rámci testu

  1. Vytvoření instance testovací třídy (konstruktoru)
  2. Nastavit TestContext vlastnost (pokud je k dispozici)
  3. Spustit GlobalTestInitialize metody
  4. Spustit TestInitialize metody (od základní k odvozené)
  5. Provedení testovací metody
  6. Aktualizace TestContext s výsledky (například Outcome vlastnost)
  7. Metody spuštění TestCleanup (odvozené ze základu)
  8. Spustit GlobalTestCleanup metody
  9. Spusťte DisposeAsync (pokud je implementováno)
  10. Spusťte Dispose (pokud je implementováno)

Návod

Související analyzátory:

Osvědčené postupy

  1. Použijte odpovídající obor: Umístěte nastavení na nejvyšší úroveň, která dává smysl vyhnout se redundantní práci.

  2. Udržujte nastavení rychle: Dlouhotrvající nastavení má vliv na všechny testy. Zvažte línou inicializaci u náročných prostředků.

  3. Správně vyčistěte: Vždy pečlivě uvolněte prostředky, k zabránění rušení testů a úniků paměti.

  4. Správně zpracujte asynchronní operace: U asynchronních metod životního cyklu používejte async Task návratové typy, ne async void.

  5. Zvažte izolaci testů: Každý test by měl být nezávislý. Vyhněte se sdílenému proměnlivému stavu mezi testy.

  6. Používejte GlobalTest střídmě: Globální metody životního cyklu se spouštějí pro každý test, takže je udržujte jednoduché a lehké.

Viz také