Compartir por


Ejecución y control de pruebas en MSTest

MSTest proporciona atributos para controlar cómo se ejecutan las pruebas, incluida la paralelización, los modelos de subprocesos, los tiempos de espera, los reintentos y la ejecución condicional en función de la plataforma o el entorno.

Atributos de hilos

Los atributos de subproceso controlan qué métodos de prueba de modelo de subproceso usan. Estos atributos son esenciales al probar componentes COM, elementos de interfaz de usuario o código con requisitos específicos de subprocesos.

STATestClassAttribute

STATestClassAttribute ejecuta todos los métodos de prueba de una clase (incluido ClassInitialize y ClassCleanup) en un apartamento con un único subproceso (STA). Use este atributo al probar objetos COM que requieren STA.

[STATestClass]
public class ComInteropTests
{
    [TestMethod]
    public void TestComComponent()
    {
        // This test runs in an STA thread
        var comObject = new SomeComObject();
        // Test COM interactions
    }
}

Nota:

Este atributo solo se admite en Windows en MSTest v3.6 y versiones posteriores.

STATestMethodAttribute

STATestMethodAttribute ejecuta un método de prueba específico en un apartamento de un solo subproceso. Use este atributo para pruebas individuales que necesitan STA mientras que otras pruebas de la clase no.

[TestClass]
public class MixedThreadingTests
{
    [STATestMethod]
    public void TestRequiringSTA()
    {
        // This test runs in an STA thread
    }

    [TestMethod]
    public void RegularTest()
    {
        // This test uses default threading
    }
}

Nota:

Este atributo solo se admite en Windows en MSTest v3.6 y versiones posteriores.

Conservar el contexto STA para las continuaciones asincrónicas

A partir de MSTest 4.1, STATestMethodAttribute incluye la UseSTASynchronizationContext propiedad que garantiza que las continuaciones asincrónicas se ejecuten en el mismo hilo STA. Cuando está habilitado, el atributo crea un SynchronizationContext personalizado que envía continuaciones al subproceso STA, lo cual es esencial para probar componentes de interfaz de usuario que requieren un subproceso STA durante sus operaciones asincrónicas.

[TestClass]
public class UIComponentTests
{
    [STATestMethod(UseSTASynchronizationContext = true)]
    public async Task TestAsyncUIOperation()
    {
        // Initial code runs on STA thread
        var control = new MyControl();
        
        await control.LoadDataAsync();
        
        // Continuation also runs on STA thread,
        // ensuring UI operations remain valid
        Assert.IsTrue(control.IsDataLoaded);
    }
}

Sugerencia

Se usa UseSTASynchronizationContext = true al probar componentes de Windows Forms o WPF que realizan operaciones asincrónicas y esperan que sus continuaciones se ejecuten en el mismo subproceso.

UITestMethodAttribute

El UITestMethod atributo programa la ejecución de pruebas en el subproceso de interfaz de usuario. Este atributo está diseñado para probar aplicaciones para UWP y WinUI que requieren acceso a subprocesos de interfaz de usuario.

[TestClass]
public class WinUITests
{
    [UITestMethod]
    public void TestUIComponent()
    {
        // This test runs on the UI thread
        var button = new Button();
        button.Content = "Click me";
        Assert.IsNotNull(button.Content);
    }
}

Nota:

Este atributo requiere el adaptador MSTest adecuado para plataformas UWP o WinUI. Para obtener más información, consulte la sección compatibilidad con la plataforma .

Atributos de paralelización

Los atributos de paralelización controlan si y cómo se ejecutan las pruebas simultáneamente, lo que mejora el tiempo de ejecución de pruebas.

ParallelizeAttribute

De forma predeterminada, MSTest ejecuta pruebas secuencialmente. El ParallelizeAttribute atributo de nivel de ensamblado habilita la ejecución de pruebas paralelas.

using Microsoft.VisualStudio.TestTools.UnitTesting;

[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)]

Ámbito de paralelización

Ámbito Comportamiento
ClassLevel Varias clases de prueba se ejecutan en paralelo, pero las pruebas dentro de una clase se ejecutan secuencialmente
MethodLevel Los métodos de prueba individuales se pueden ejecutar en paralelo, independientemente de su clase.

Subprocesos de trabajo

La Workers propiedad especifica el número máximo de subprocesos para la ejecución en paralelo:

  • 0 (valor predeterminado): use el número de procesadores lógicos en la máquina.
  • Cualquier número entero positivo: utilice ese número específico de hilos.
// Parallelize at class level with 2 worker threads
[assembly: Parallelize(Workers = 2, Scope = ExecutionScope.ClassLevel)]

Sugerencia

También puede configurar la paralelización mediante runsettings o testconfig.json sin modificar el código.

Sugerencia

Habilite la paralelización en el nivel de ensamblado de forma predeterminada, incluso si muchas pruebas requieren actualmente una ejecución secuencial. Este enfoque fomenta la escritura de nuevas pruebas que admiten la ejecución en paralelo desde el principio. Use el analizador de MSTEST0001 para asegurarse de que cada clase de prueba declara explícitamente su intención de paralelización, lo que le obliga a revisar si cada clase admite de forma segura la ejecución simultánea. A menudo, excluyendo solo algunas clases o métodos con DoNotParallelize es suficiente, lo que permite que la mayoría de las pruebas se ejecuten en paralelo para una ejecución de prueba significativamente más rápida.

DoNotParallelizeAttribute

El DoNotParallelizeAttribute impide la ejecución en paralelo de ensamblados, clases o métodos específicos. Use este atributo cuando las pruebas compartan el estado o los recursos a los que no se pueda acceder de forma segura simultáneamente.

[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

[TestClass]
public class ParallelTests
{
    [TestMethod]
    public void CanRunInParallel()
    {
        // This test can run with others
    }
}

[TestClass]
[DoNotParallelize]
public class SequentialTests
{
    [TestMethod]
    public void MustRunSequentially()
    {
        // This class's tests run sequentially
    }
}

[TestClass]
public class MixedTests
{
    [TestMethod]
    public void CanRunInParallel()
    {
        // This test can run with others
    }

    [TestMethod]
    [DoNotParallelize]
    public void MustBeIsolated()
    {
        // This specific test doesn't run in parallel
    }
}

Nota:

Solo necesita DoNotParallelize cuando ha habilitado la ejecución en paralelo con el Parallelize atributo .

Atributos de tiempo de espera

Los atributos de tiempo de espera impiden que las pruebas se ejecuten indefinidamente y ayuden a identificar problemas de rendimiento.

TimeoutAttribute

TimeoutAttribute especifica el tiempo máximo (en milisegundos) durante el cual puede ejecutarse un método de prueba o de configuración. Si la ejecución supera este tiempo, se produce un error en la prueba.

[TestClass]
public class TimeoutTests
{
    [TestMethod]
    [Timeout(5000)] // 5 seconds
    public void TestWithTimeout()
    {
        // Test must complete within 5 seconds
    }
}

Sugerencia

Puede configurar un tiempo de espera de prueba global a través de runsettings (TestTimeout) o testconfig.json (timeout.test) sin modificar el código.

Aplicar tiempo de espera a los métodos de prueba

También puede aplicar tiempos de espera a los métodos de inicialización y limpieza:

[TestClass]
public class FixtureTimeoutTests
{
    [ClassInitialize]
    [Timeout(10000)]
    public static void ClassInit(TestContext context)
    {
        // Must complete within 10 seconds
    }

    [TestInitialize]
    [Timeout(2000)]
    public void TestInit()
    {
        // Must complete within 2 seconds
    }
}

Sugerencia

Cada método de accesorio que acepta un [Timeout] atributo tiene una configuración global equivalente. Configure los tiempos de espera globalmente a través de runsettings o testconfig.json mediante configuraciones como TestInitializeTimeout, ClassInitializeTimeout, AssemblyInitializeTimeout y sus equivalentes de limpieza.

Nota:

No se garantiza que los tiempos de espera sean precisamente precisos. La prueba se anula una vez transcurrido el tiempo especificado, pero la cancelación real puede tardar un poco más.

Cancelación cooperativa

De forma predeterminada, MSTest encapsula cada método de prueba cronometrado en una tarea o subproceso independiente. Cuando se alcanza el tiempo de espera, el marco deja de observar la prueba, pero la tarea subyacente continúa ejecutándose en segundo plano. Este comportamiento puede causar problemas:

  • El método de prueba continúa accediendo a los recursos y mutando el estado incluso después del tiempo de espera.
  • La ejecución en segundo plano puede provocar condiciones de carrera que afecten a las pruebas posteriores.
  • Cada método temporizado incurre en una sobrecarga adicional del contenedor de tareas o hilos.

A partir de MSTest 3.6, use la CooperativeCancellation propiedad para evitar estos problemas. En modo cooperativo, MSTest no ajusta la prueba en una tarea adicional. En su lugar, cuando se alcanza el tiempo de espera, el framework señaliza el token de cancelación. El código de prueba es responsable de comprobar el token periódicamente y finalizar de manera suave.

[TestClass]
public class CooperativeTimeoutTests
{
    [TestMethod]
    [Timeout(5000, CooperativeCancellation = true)]
    public async Task TestWithCooperativeCancellation(CancellationToken cancellationToken)
    {
        // Check the token periodically
        while (!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(100, cancellationToken);
            // Do work
        }
    }

    [TestMethod]
    [Timeout(5000, CooperativeCancellation = true)]
    public void SyncTestWithCooperativeCancellation(CancellationToken cancellationToken)
    {
        // Works with sync methods too
        for (int i = 0; i < 1000; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            // Do work
        }
    }
}

Beneficios de cancelación cooperativa:

  • Menor sobrecarga de rendimiento (sin contenedor adicional de tareas o subprocesos por prueba).
  • Limpieza de recursos más limpia, ya que el código controla la cancelación explícitamente.
  • Se alinea con los patrones de cancelación estándar de .NET.
  • Comportamiento determinista al evitar condiciones de carrera entre el código de prueba y la ejecución en segundo plano no observada.

Nota:

La cancelación cooperativa requiere que el código de prueba compruebe el token de cancelación con regularidad. Si el código no comprueba el token, la prueba no se detendrá realmente cuando se alcance el tiempo de espera.

Sugerencia

Puede habilitar la cancelación cooperativa globalmente para todos los atributos de tiempo de espera a través de runsettings o testconfig.json en lugar de establecerlo en cada atributo individualmente.

Sugerencia

Analizadores relacionados:

  • MSTEST0045 : recomienda usar la cancelación cooperativa para los atributos de tiempo de espera.

Atributos de reintento

Los atributos de reintento ayudan a controlar las pruebas poco confiables mediante la repetición automática de las pruebas con errores.

RetryAttribute

RetryAttribute, introducido en MSTest 3.8, reintenta automáticamente los métodos de test que fallan o expiran. Configure los intentos de reintento máximos, los retrasos entre reintentos y la estrategia de backoff.

[TestClass]
public class RetryTests
{
    [TestMethod]
    [Retry(3)] // Retry up to 3 times if the test fails
    public void FlakeyNetworkTest()
    {
        // Test that might occasionally fail due to network issues
    }

    [TestMethod]
    [Retry(3, MillisecondsDelayBetweenRetries = 1000, BackoffType = DelayBackoffType.Exponential)]
    public void TestWithExponentialBackoff()
    {
        // Retries with increasing delays: 1s, 2s, 4s
    }

    [TestMethod]
    [Retry(5, MillisecondsDelayBetweenRetries = 500, BackoffType = DelayBackoffType.Constant)]
    public void TestWithConstantDelay()
    {
        // Retries with constant 500ms delay between attempts
    }
}

Opciones de configuración

Propiedad Description Predeterminado
MaxRetryAttempts Número máximo de reintentos (solo lectura, establecido a través del constructor) Obligatorio
MillisecondsDelayBetweenRetries Retraso base entre reintentos (en ms) 0
BackoffType Constant o Exponential retraso Constant

Nota:

Solo un RetryAttribute puede estar presente en un método de prueba. No se puede usar RetryAttribute en métodos que no estén marcados con TestMethod.

Sugerencia

Analizadores relacionados:

  • MSTEST0043 : recomienda usar RetryAttribute en métodos de prueba.

Implementaciones de reintento personalizadas

Cree lógica de reintento personalizada heredando de RetryBaseAttribute:

public class CustomRetryAttribute : RetryBaseAttribute
{
    private readonly int _maxRetries;

    public CustomRetryAttribute(int maxRetries)
    {
        _maxRetries = maxRetries;
    }

    // Implement abstract members
    // Add custom logic for retry conditions
}

Atributos para la ejecución condicional

Los atributos de ejecución condicional controlan si las pruebas se ejecutan en función de condiciones específicas, como el sistema operativo o el entorno de CI.

ConditionBaseAttribute

ConditionBaseAttribute es la clase base abstracta para la ejecución condicional. MSTest proporciona varias implementaciones integradas.

Nota:

De forma predeterminada, los atributos de condición no se heredan. Aplicarlos a una clase base no afecta a las clases derivadas. Los atributos de condición personalizados pueden invalidar este comportamiento redefinindo AttributeUsage, pero no se recomienda mantener la coherencia con los atributos de condición integrados.

Sugerencia

Analizadores relacionados:

  • MSTEST0041 : recomienda usar atributos basados en condiciones con clases de prueba.

OSConditionAttribute

Ejecuta OSConditionAttribute o omite pruebas basadas en el sistema operativo. Use la OperatingSystems enumeración flags para especificar qué sistemas operativos se aplican.

[TestClass]
public class OSSpecificTests
{
    [TestMethod]
    [OSCondition(OperatingSystems.Windows)]
    public void WindowsOnlyTest()
    {
        // Runs only on Windows
    }

    [TestMethod]
    [OSCondition(OperatingSystems.Linux | OperatingSystems.OSX)]
    public void UnixLikeOnlyTest()
    {
        // Runs on Linux or macOS
    }

    [TestMethod]
    [OSCondition(ConditionMode.Exclude, OperatingSystems.Windows)]
    public void SkipOnWindowsTest()
    {
        // Runs on any OS except Windows
    }
}

Sistemas operativos compatibles

Sistema operativo Description
Windows Microsoft Windows
Linux Distribuciones de Linux
OSX macOS
FreeBSD FreeBSD

Combine sistemas operativos con el operador OR bit a bit (|).

Sugerencia

Analizadores relacionados:

  • MSTEST0061 : recomienda usar OSCondition el atributo en lugar de las comprobaciones en tiempo de ejecución.

CIConditionAttribute

El componente CIConditionAttribute ejecuta o omite las pruebas en función de si se ejecutan en un entorno de integración continua.

[TestClass]
public class CIAwareTests
{
    [TestMethod]
    [CICondition] // Default: runs only in CI
    public void CIOnlyTest()
    {
        // Runs only in CI environments
    }

    [TestMethod]
    [CICondition(ConditionMode.Include)]
    public void ExplicitCIOnlyTest()
    {
        // Same as above, explicitly stated
    }

    [TestMethod]
    [CICondition(ConditionMode.Exclude)]
    public void LocalDevelopmentOnlyTest()
    {
        // Skipped in CI, runs during local development
    }
}

IgnoreAttribute

El IgnoreAttribute omite incondicionalmente una clase o método de prueba. Opcionalmente, proporcione un motivo para omitir.

Sugerencia

Analizador relacionado: MSTEST0015 - No se debe omitir el método de prueba. Habilite este analizador para detectar pruebas que se omiten permanentemente.

[TestClass]
public class IgnoreExamples
{
    [TestMethod]
    [Ignore]
    public void TemporarilyDisabled()
    {
        // This test is skipped
    }

    [TestMethod]
    [Ignore("Waiting for bug #123 to be fixed")]
    public void DisabledWithReason()
    {
        // This test is skipped with a documented reason
    }
}

[TestClass]
[Ignore("Entire class needs refactoring")]
public class IgnoredTestClass
{
    [TestMethod]
    public void Test1() { }  // Skipped

    [TestMethod]
    public void Test2() { }  // Skipped
}

Al omitir las pruebas debido a problemas conocidos, use WorkItemAttribute o GitHubWorkItemAttribute para la rastreabilidad:

[TestClass]
public class TrackedIgnoreExamples
{
    [TestMethod]
    [Ignore("Waiting for fix")]
    [WorkItem(12345)]
    public void TestWithWorkItem()
    {
        // Linked to work item 12345
    }

    [TestMethod]
    [Ignore("Known issue")]
    [GitHubWorkItem("https://github.com/owner/repo/issues/42")]
    public void TestWithGitHubIssue()
    {
        // Linked to GitHub issue #42
    }
}

procedimientos recomendados

  1. Use la paralelización sabiamente: habilite la paralelización para pruebas independientes, pero use DoNotParallelize para las pruebas que comparten el estado.

  2. Establecer tiempos de espera adecuados: elija tiempos de espera que permitan la ejecución normal, pero capturen pruebas bloqueadas. Considere los entornos de CI lentos.

  3. Preferir la cancelación cooperativa: Utilice la cancelación cooperativa para evitar la sobrecarga de envoltorios adicionales de tareas y prevenir la ejecución en segundo plano de pruebas que superan el tiempo límite. Habilite el analizador MSTEST0045 para aplicar esta práctica.

  4. Documentar pruebas ignoradas: Proporcione siempre una razón y una referencia al elemento de trabajo al ignorar las pruebas.

  5. Use reintentos con moderación: solucione la causa principal de las pruebas poco confiables en lugar de depender de reintentos.

  6. Probar el código específico del sistema operativo correctamente: use OSCondition para ejecutar pruebas específicas de la plataforma solo cuando sean aplicables.

Consulte también