Бөлісу құралы:


Жизненный цикл MSTest

MSTest предоставляет четко определенный жизненный цикл для тестовых классов и методов тестирования, что позволяет выполнять операции установки и удаления на различных этапах выполнения теста. Понимание жизненного цикла помогает создавать эффективные тесты и избегать распространенных ошибок.

Обзор жизненного цикла

Жизненный цикл подразделяется на четыре этапа, выполняющихся с самого высокого уровня (сборка) до самого низкого уровня (метод тестирования):

  1. Уровень сборки: выполняется один раз при загрузке и выгрузке тестовой сборки
  2. Уровень класса: выполняется один раз на тестовый класс
  3. Глобальный уровень тестирования: выполняется до и после каждого метода теста в сборке
  4. Уровень теста: выполняется для каждого метода теста (включая каждую строку данных в параметризованных тестах)

Жизненный цикл на уровне сборки

Методы жизненного цикла сборки выполняются один раз при загрузке и выгрузке тестовой сборки. Используйте их для дорогостоящих однократных настроек, таких как инициализация базы данных или запуск службы.

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

Требования

  • Методы должны быть public static
  • Тип возвращаемого значения: void, Taskили ValueTask (MSTest версии 3.3+)
  • AssemblyInitialize требуется один TestContext параметр
  • AssemblyCleanup принимает ноль параметров или один TestContext параметр (MSTest 3.8+)
  • Только один из каждого атрибута, разрешенного для каждой сборки
  • Должен находиться в классе, помеченном как [TestClass]

Подсказка

Связанные анализаторы:

  • MSTEST0012 — проверяет AssemblyInitialize подпись.
  • MSTEST0013 — проверяет AssemblyCleanup подпись.

Жизненный цикл на уровне класса

Методы жизненного цикла класса выполняются один раз на тестовый класс до и после всех методов тестирования в этом классе. Используйте эти настройки для совместного использования в тестах внутри класса.

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

Требования

  • Методы должны быть public static
  • Тип возвращаемого значения: void, Taskили ValueTask (MSTest версии 3.3+)
  • ClassInitialize требуется один TestContext параметр
  • ClassCleanup принимает ноль параметров или один TestContext параметр (MSTest 3.8+)
  • Только один из каждого атрибута, разрешенных для каждого класса

Поведение наследования

ClassInitialize Управление выполнением производных классов с помощью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
    }
}
Поведение наследования Description
None (по умолчанию) Инициализация выполняется только для декларативного класса
BeforeEachDerivedClass Инициализация выполняется перед каждым производным классом

Подсказка

Связанные анализаторы:

  • MSTEST0010 — проверяет ClassInitialize подпись.
  • MSTEST0011 — проверяет ClassCleanup подпись.
  • MSTEST0034 — рекомендует использовать ClassCleanupBehavior.EndOfClass.

Жизненный цикл глобального уровня тестирования

Замечание

Глобальные атрибуты жизненного цикла тестирования были представлены в MSTest 3.10.0.

Глобальные методы жизненного цикла тестирования выполняются до и после каждого метода теста во всей сборке без необходимости добавлять код в каждый тестовый класс.

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

Требования

  • Методы должны быть public static
  • Тип возвращаемого значения: void, Taskили ValueTask
  • Должен иметь ровно один TestContext параметр
  • Должен находиться в классе, помеченном как [TestClass]
  • В сборке разрешается использование нескольких методов, имеющих эти атрибуты.

Замечание

Когда существует несколько методов GlobalTestInitialize или GlobalTestCleanup, порядок их выполнения не гарантирован. TimeoutAttribute не поддерживается методами GlobalTestInitialize.

Подсказка

Связанный анализатор: MSTEST0050 — проверяет глобальные методы тестирования.

Жизненный цикл тестов

Жизненный цикл уровня теста выполняется для каждой тестовой методики. Для параметризованных тестов жизненный цикл выполняется для каждой строки данных.

Этап установки

Используйте TestInitialize или конструктор для настройки для каждого теста:

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

Конструктор vs. TestInitialize:

Аспект Конструктор TestInitialize
Асинхронная поддержка нет Да
Поддержка времени ожидания нет Да (с атрибутом [Timeout] )
Порядок выполнения First После конструктора
Наследство База, а затем производная База, а затем производная
Поведение исключений Очистка и удаление не выполняются (инстанция не существует) Очистка и удаление по-прежнему выполняются

Подсказка

Какой подход следует использовать? Конструкторы обычно предпочтительнее, так как они позволяют использовать readonly поля, что обеспечивает неизменяемость и делает ваш тестовый класс проще для анализа. Используйте TestInitialize, когда вам требуется асинхронная инициализация или поддержка времени ожидания.

Вы также можете объединить оба подхода: используйте конструктор для простой readonly синхронной инициализации полей, а TestInitialize также для дополнительной асинхронной настройки, которая зависит от этих полей.

При необходимости можно включить анализаторы кода для обеспечения согласованного подхода:

  • MSTEST0019 . Предпочитать методы TestInitialize вместо конструкторов
  • MSTEST0020 . Предпочитать конструкторы методам TestInitialize

Этап выполнения

Метод теста выполняется после завершения установки. Для async методов тестирования MSTest ожидает возвращаемого Task или ValueTask.

Предупреждение

По умолчанию асинхронные методы тестирования не имеют SynchronizationContext . Это не относится к UITestMethod тестам в UWP и WinUI, которые выполняются в потоке пользовательского интерфейса.

Этап очистки

Используйте TestCleanup или IDisposable/IAsyncDisposable для очистки после каждого теста.

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

Порядок выполнения очистки (производный от базы):

  1. TestCleanup (производный класс)
  2. TestCleanup (базовый класс)
  3. DisposeAsync (если реализовано)
  4. Dispose (если реализовано)

Подсказка

При необходимости можно включить анализаторы кода для обеспечения согласованного подхода к очистке:

  • MSTEST0021 — предпочитайте метод Dispose вместо методов TestCleanup
  • MSTEST0022 — предпочитать TestCleanup вместо методов Dispose

Если у вас включены анализаторы, отличные от MSTest, например правила анализа кода .NET, вы можете увидеть правило CA1001, которое предлагает реализовать шаблон освобождения, если ваш класс тестирования владеет ресурсами, которые можно удалить. Это ожидаемое поведение, и вы должны следовать инструкциям анализатора.

Завершение заказа на уровне теста

  1. Создание экземпляра тестового класса (конструктора)
  2. Установите свойство TestContext (если присутствует)
  3. Запуск методов GlobalTestInitialize
  4. Выполнить методы TestInitialize (от базового к производному)
  5. Выполнение метода тестирования
  6. Обновление TestContext с результатами (например, Outcome свойство)
  7. Запустить методы TestCleanup (от производного к базовому)
  8. Запуск методов GlobalTestCleanup
  9. Запустите DisposeAsync (если реализовано)
  10. Запустите Dispose (если реализовано)

Подсказка

Связанные анализаторы:

  • MSTEST0008 — проверяет TestInitialize подпись.
  • MSTEST0009 — проверяет TestCleanup подпись.
  • MSTEST0063 — проверяет конструктор тестового класса.

Лучшие практики

  1. Используйте соответствующую область: разместите настройку на самом высоком уровне, чтобы избежать избыточной работы.

  2. Поддерживайте быструю настройку: длительная настройка влияет на все тесты. Рассмотрите отложенную инициализацию для дорогостоящих ресурсов.

  3. Правильная очистка: всегда освобождайте ресурсы, чтобы предотвратить помехи в тестах и утечки памяти.

  4. Правильно обрабатывать асинхронные процессы: используйте async Task типы возвращаемых значений, а не async void, для асинхронных методов жизненного цикла.

  5. Рассмотрим изоляцию тестов: каждый тест должен быть независимым. Избегайте общего изменяемого состояния между тестами.

  6. Используйте GlobalTest экономно: глобальные методы жизненного цикла выполняются для каждого теста, поэтому они должны быть легкими (незатейливыми).

См. также