Udostępnij przez


Cykl życia msTest

Narzędzie MSTest zapewnia dobrze zdefiniowany cykl życia dla klas testowych i metod testowych, co umożliwia wykonywanie operacji konfiguracji i usuwania na różnych etapach wykonywania testów. Zrozumienie cyklu życia ułatwia pisanie wydajnych testów i unikanie typowych pułapek.

Przegląd cyklu życia

Grupy cyklu życia dzielą się na cztery etapy, wykonywane od najwyższego poziomu (montażu) do najniższego poziomu (metoda testu).

  1. Poziom zestawu: uruchamia się raz, gdy zestaw testów ładuje i rozładowuje
  2. Poziom klasy: uruchamiane jest raz na klasę testową
  3. Globalny poziom testów: Uruchamia się przed każdą metodą testową i po niej w asemblerze
  4. Poziom testu: Uruchamiane dla każdej metody testowej (w tym każdego wiersza danych w sparametryzowanych testach)

Cykl życia na poziomie zestawu

Metody cyklu życia modułu są uruchamiane raz, gdy moduł testowy ładowany jest do pamięci i usuwany. Użyj ich w przypadku kosztownej jednorazowej konfiguracji, takiej jak inicjowanie bazy danych lub uruchamianie usługi.

AssemblyInitialize i 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!);
    }
}

Requirements

  • Metody muszą być public static
  • Zwracany typ: void, Tasklub ValueTask (MSTest w wersji 3.3 lub nowszej)
  • AssemblyInitialize wymaga jednego TestContext parametru
  • AssemblyCleanup akceptuje zero parametrów lub jeden TestContext parametr (MSTest 3.8+)
  • Tylko jeden z atrybutów dozwolonych dla zestawu
  • Musi znajdować się w klasie oznaczonej [TestClass]

Wskazówka

Pomocnicze analizatory

  • MSTEST0012 — weryfikuje AssemblyInitialize podpis.
  • MSTEST0013 — weryfikuje AssemblyCleanup podpis.

Cykl życia na poziomie klasy

Metody cyklu życia klasy są uruchamiane jednorazowo dla każdej klasy testowej, przed oraz po wykonaniu wszystkich metod testowych w tej klasie. Użyj tego do konfiguracji wspólnej dla testów w klasie.

ClassInitialize i 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()
    {
        var response = await _client!.GetAsync("/users");
        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}

Requirements

  • Metody muszą być public static
  • Zwracany typ: void, Tasklub ValueTask (MSTest w wersji 3.3 lub nowszej)
  • ClassInitialize wymaga jednego TestContext parametru
  • ClassCleanup akceptuje zero parametrów lub jeden TestContext parametr (MSTest 3.8+)
  • Tylko jeden z atrybutów dozwolonych dla każdej klasy

Zachowanie dziedziczenia

Określ, czy ClassInitialize działa dla klas pochodnych przy użyciu polecenia 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
    }
}
Dziedziczenie Behawioralne Description
None (ustawienie domyślne) Inicjalizowanie działań tylko dla klasy ogłaszającej
BeforeEachDerivedClass Inicjowanie przed uruchomieniem każdej klasy pochodnej

Wskazówka

Powiązane analizatory:

  • MSTEST0010 — weryfikuje ClassInitialize podpis.
  • MSTEST0011 — weryfikuje ClassCleanup podpis.
  • MSTEST0034 — zaleca użycie ClassCleanupBehavior.EndOfClass.

Globalny poziomowy cykl życia testów

Uwaga / Notatka

Globalne atrybuty cyklu życia testu zostały wprowadzone w narzędziu MSTest 3.10.0.

Globalne metody cyklu życia testu są uruchamiane przed i po każdej metodzie testowej w całym zestawie bez konieczności dodawania kodu do każdej klasy testowej.

GlobalTestInitialize i 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}");
    }
}

Requirements

  • Metody muszą być public static
  • Typ zwracany: void, lub TaskValueTask
  • Musi mieć dokładnie jeden TestContext parametr
  • Musi znajdować się w klasie oznaczonej [TestClass]
  • Wiele metod z tymi atrybutami jest dozwolonych w zestawie

Uwaga / Notatka

Jeśli istnieje wiele GlobalTestInitialize metod lub GlobalTestCleanup , kolejność wykonywania nie jest gwarantowana. Metody TimeoutAttribute nie są obsługiwane na metodach GlobalTestInitialize.

Wskazówka

Powiązany analizator: MSTEST0050 — weryfikuje globalne metody fikstury testowej.

Cykl życia testów

Cykl życia na poziomie testowym uruchamia się dla każdej metody testowej. W przypadku testów sparametryzowanych cykl życia jest uruchamiany dla każdego wiersza danych.

Faza instalacji

Użyj TestInitialize lub konstruktora do konfiguracji przed-testowej.

[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()
    {
        var result = _calculator!.Add(2, 3);
        Assert.AreEqual(5, result);
    }
}

Konstruktor a TestInitialize:

Aspekt Konstruktor InicjalizacjaTestu
Obsługa asynchroniczna Nie. Tak
Obsługa limitu czasu Nie. Tak (z atrybutem [Timeout] )
Kolejność wykonywania First Po konstruktorze
Dziedziczenie Podstawa, a następnie pochodne Podstawa, a następnie pochodne
Zachowanie wyjątku Czyszczenie i usuwanie nie są uruchamiane (żadne wystąpienie nie istnieje) Czyszczenie i usuwanie nadal działają

Wskazówka

Którego podejścia należy użyć? Konstruktory są zazwyczaj preferowane, ponieważ umożliwiają korzystanie z pól readonly, co wymusza niezmienność i ułatwia zrozumienie klasy testowej. Użyj TestInitialize, jeśli potrzebujesz obsługi inicjowania asynchronicznego lub limitu czasu.

Można również połączyć obie metody: użyj konstruktora do prostego synchronicznego inicjowania readonly pól i TestInitialize do dodatkowej asynchronicznej konfiguracji zależnej od tych pól.

Opcjonalnie możesz włączyć analizatory kodu, aby wymusić spójne podejście:

  • MSTEST0019 — preferuj metody TestInitialize nad konstruktorami
  • MSTEST0020 — preferuj konstruktory zamiast metod TestInitialize

Faza realizacji

Metoda testowa jest wykonywana po zakończeniu instalacji. W przypadku async metod testowych narzędzie MSTest oczekuje na zwrócony Task element lub ValueTask.

Ostrzeżenie

Metody testowe asynchroniczne nie mają SynchronizationContext domyślnie. Nie dotyczy to testów UITestMethod w UWP i WinUI, uruchamianych w wątku interfejsu użytkownika.

Faza oczyszczania

Użyj TestCleanup albo IDisposable/IAsyncDisposable do oczyszczania poszczególnych testów.

[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()
    {
        var response = await _client!.GetAsync("https://example.com");
        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}

Kolejność wykonywania oczyszczania (pochodna do podstawy):

  1. TestCleanup (klasa pochodna)
  2. TestCleanup (klasa bazowa)
  3. DisposeAsync (jeśli zaimplementowano)
  4. Dispose (jeśli zaimplementowano)

Wskazówka

Opcjonalnie możesz włączyć analizatory kodu, aby wymusić spójne podejście oczyszczania:

  • MSTEST0021 — preferuj metodę Dispose nad metodami TestCleanup
  • MSTEST0022 — preferuj metodę TestCleanup nad metodami Dispose

Jeśli włączono analizatory inne niż MSTest, takie jak reguły analizy kodu platformy .NET, może zostać wyświetlony komunikat CA1001 sugerujący implementację wzorca usuwania, gdy klasa testowa jest właścicielem jednorazowych zasobów. Jest to oczekiwane zachowanie i należy postępować zgodnie ze wskazówkami analizatora.

Zrealizowane zamówienie na poziomie testowym

  1. Utworzenie instancji klasy testowej (konstruktor)
  2. Ustaw TestContext właściwość (jeśli jest obecna)
  3. Uruchom metody GlobalTestInitialize
  4. Uruchom TestInitialize metody (od podstawowych do pochodnych)
  5. Wykonaj metodę testową
  6. Aktualizuj TestContext przy użyciu wyników (na przykład właściwości Outcome)
  7. Uruchamianie TestCleanup metod (pochodnych do podstawy)
  8. Uruchom metody GlobalTestCleanup
  9. Uruchom DisposeAsync (jeśli zaimplementowano)
  10. Uruchom Dispose (jeśli zaimplementowano)

Wskazówka

Powiązane analizatory:

  • MSTEST0008 — weryfikuje TestInitialize podpis.
  • MSTEST0009 — weryfikuje TestCleanup podpis.
  • MSTEST0063 — weryfikuje konstruktor klasy testowej.

Najlepsze rozwiązania

  1. Użyj odpowiedniego zakresu: umieść ustawienie na najwyższym poziomie, który ma sens, aby uniknąć nadmiarowej pracy.

  2. Szybkie konfigurowanie: długotrwała konfiguracja ma wpływ na wszystkie testy. Rozważ opóźnioną inicjalizację kosztownych zasobów.

  3. Czyszczaj poprawnie: zawsze czyścić zasoby, aby zapobiec zakłóceniom testów i wyciekom pamięci.

  4. Prawidłowa obsługa asynchroniczna: używaj async Task typów zwracania, zamiast async void, dla metod cyklu życia asynchronicznego.

  5. Rozważ izolację testu: każdy test powinien być niezależny. Unikaj współdzielonego stanu zmiennego między testami.

  6. Używaj GlobalTest oszczędnie: Globalne metody cyklu życia są wykonywane dla każdego testu, więc powinny być efektywne i proste.

Zobacz także