Compartir a través de


Ciclo de vida de MSTest

MSTest proporciona un ciclo de vida bien definido para las clases de prueba y los métodos de prueba, lo que le permite realizar operaciones de configuración y desmontaje en varias fases de ejecución de pruebas. Comprender el ciclo de vida le ayuda a escribir pruebas eficaces y a evitar problemas comunes.

Introducción al ciclo de vida

El ciclo de vida se agrupa en cuatro fases, ejecutado desde el nivel más alto (ensamblado) hasta el nivel más bajo (método de prueba):

  1. Nivel de ensamblado: se ejecuta una vez cuando el ensamblado de prueba se carga y descarga
  2. Nivel de clase: se ejecuta una vez por clase de prueba
  3. Nivel de prueba global: se ejecuta antes y después de cada método de prueba del ensamblado
  4. Nivel de prueba: se ejecuta para cada método de prueba (incluida cada fila de datos en pruebas con parámetros)

Ciclo de vida a nivel de ensamblaje

Los métodos de ciclo de vida del ensamblado se ejecutan una vez cuando el ensamblado de prueba se carga y descarga. Úselos para una configuración única costosa, como la inicialización de la base de datos o el inicio del servicio.

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

Requisitos

  • Los métodos deben ser public static
  • Tipo de valor devuelto: void, Tasko ValueTask (MSTest v3.3+)
  • AssemblyInitialize requiere un TestContext parámetro
  • AssemblyCleanup acepta cero parámetros o un TestContext parámetro (MSTest 3.8+)
  • Solo se permite uno de cada atributo por cada ensamblaje
  • Debe estar en una clase marcada con [TestClass]

Sugerencia

Analizadores relacionados:

Ciclo de vida a nivel de clase

Los métodos de ciclo de vida de clase se ejecutan una vez por clase de prueba, antes y después de todos los métodos de prueba de esa clase. Úselos para la configuración compartida entre tests en una clase.

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

Requisitos

  • Los métodos deben ser public static
  • Tipo de valor devuelto: void, Tasko ValueTask (MSTest v3.3+)
  • ClassInitialize requiere un TestContext parámetro
  • ClassCleanup acepta cero parámetros o un TestContext parámetro (MSTest 3.8+)
  • Solo uno de cada atributo permitido por clase

Comportamiento de herencia

Controlar si ClassInitialize se ejecuta para clases derivadas mediante 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
    }
}
InheritanceBehavior Description
None (valor predeterminado) Inicializar solo se ejecuta para la clase declarante.
BeforeEachDerivedClass Inicializar ejecuciones antes de cada clase derivada

Sugerencia

Analizadores relacionados:

  • MSTEST0010 : valida la ClassInitialize firma.
  • MSTEST0011 : valida la ClassCleanup firma.
  • MSTEST0034 : recomienda usar ClassCleanupBehavior.EndOfClass.

Ciclo de vida de nivel de prueba global

Nota:

Los atributos de ciclo de vida de pruebas globales se introdujeron en MSTest 3.10.0.

Los métodos de ciclo de vida de pruebas globales se ejecutan antes y después de cada método de prueba en todo el ensamblado, sin necesidad de agregar código a cada clase de prueba.

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

Requisitos

  • Los métodos deben ser public static
  • Tipo de valor devuelto: void, Tasko ValueTask
  • Debe tener exactamente un TestContext parámetro
  • Debe estar en una clase marcada con [TestClass]
  • Se permiten varios métodos con estos atributos en el ensamblado

Nota:

Cuando existen varios GlobalTestInitialize métodos o GlobalTestCleanup , no se garantiza el orden de ejecución. No se admite TimeoutAttribute en métodos GlobalTestInitialize.

Sugerencia

Analizador relacionado: MSTEST0050 : valida métodos globales de accesorios de prueba.

Ciclo de vida de nivel de prueba

El ciclo de vida a nivel de prueba se ejecuta para cada método de prueba. Para las pruebas con parámetros, el ciclo de vida se ejecuta para cada fila de datos.

Fase de configuración

Utilice TestInitialize o un constructor para la configuración para cada prueba.

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

Constructor frente a TestInitialize:

Aspecto Constructor TestInitialize
Compatibilidad asincrónica No
Soporte de timeout No Sí (con [Timeout] atributo)
Orden de ejecución Primero Después del constructor
Herencia Base a continuación derivada Base a continuación derivada
Comportamiento de excepción Las funciones de limpieza y eliminación no se ejecutan (no existe ninguna instancia) Limpieza y eliminación se siguen ejecutando

Sugerencia

¿Qué enfoque debo usar? Los constructores suelen ser preferidos porque permiten usar readonly campos, lo que impone inmutabilidad y hace que la clase de prueba sea más comprensible. Use TestInitialize cuando necesite compatibilidad con la inicialización asincrónica o el tiempo de espera.

También puede combinar ambos enfoques: use el constructor para la inicialización sincrónica simple de readonly campos y TestInitialize para una configuración asincrónica adicional que dependa de esos campos.

Opcionalmente, puede habilitar analizadores de código para aplicar un enfoque coherente:

  • MSTEST0019: preferir métodos TestInitialize sobre constructores
  • MSTEST0020 : se prefieren constructores sobre los métodos TestInitialize

Fase de ejecución

El método de prueba se ejecuta una vez completada la instalación. Para los métodos de prueba async, MSTest espera el retorno de Task o ValueTask.

Advertencia

Los métodos de prueba asincrónicos no tienen un SynchronizationContext por defecto. Esto no se aplica a las pruebas de UITestMethod en UWP y WinUI, que se ejecutan en el hilo de la interfaz de usuario.

Fase de limpieza

Utilice TestCleanup o IDisposable/IAsyncDisposable para limpiar después de cada prueba:

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

Orden de ejecución de limpieza (derivado a base):

  1. TestCleanup (clase derivada)
  2. TestCleanup (clase base)
  3. DisposeAsync (si se implementa)
  4. Dispose (si se implementa)

Sugerencia

Opcionalmente, puede habilitar analizadores de código para aplicar un enfoque de limpieza coherente:

  • MSTEST0021 - Prefiera Dispose sobre los métodos TestCleanup
  • MSTEST0022 : preferir TestCleanup sobre los métodos Dispose

Si tiene habilitados analizadores que no son de MSTest, como reglas de análisis de código de .NET, es posible que vea CA1001 sugiriendo que implemente el patrón de eliminación cuando la clase de prueba posee recursos desechables. Este es el comportamiento esperado y debe seguir las instrucciones del analizador.

Completar el orden de nivel de prueba

  1. Creación de una instancia de la clase de prueba (constructor)
  2. Establecer la propiedad TestContext (si está presente)
  3. Ejecutar métodos GlobalTestInitialize
  4. Ejecutar métodos TestInitialize (de base a derivado)
  5. Ejecutar método de prueba
  6. Actualiza TestContext con los resultados (por ejemplo, la propiedad Outcome)
  7. Ejecutar TestCleanup métodos (derivados a base)
  8. Ejecuta métodos GlobalTestCleanup
  9. Ejecutar DisposeAsync (si está implementado)
  10. Ejecutar Dispose (si está implementado)

Sugerencia

Analizadores relacionados:

procedimientos recomendados

  1. Usar el ámbito adecuado: coloque la configuración en el nivel más alto que tenga sentido para evitar el trabajo redundante.

  2. Mantener la configuración rápida: la configuración prolongada afecta a todas las pruebas. Considere la posibilidad de inicialización diferida para recursos costosos.

  3. Limpiar correctamente: limpie siempre los recursos para evitar fugas de memoria y interferencias de prueba.

  4. Manejar correctamente async: use async Task tipos de retorno, no async void, para métodos de ciclo de vida asincrónicos.

  5. Considere el aislamiento de las pruebas: cada prueba debe ser independiente. Evite el estado mutable compartido entre las pruebas.

  6. Use GlobalTest con moderación: los métodos de ciclo de vida global se ejecutan para cada prueba, por lo que deben mantenerse ligeros.

Consulte también