Pruebas controladas por datos en MSTest

Las pruebas controladas por datos permiten ejecutar el mismo método de prueba con varios conjuntos de datos de entrada. En lugar de escribir métodos de prueba independientes para cada caso de prueba, defina la lógica de prueba una vez y proporcione entradas diferentes a través de atributos o orígenes de datos externos.

Información general

MSTest proporciona varios atributos para las pruebas controladas por datos:

Atributo Caso de uso Más adecuado para
DataRow Datos de prueba en línea Casos de prueba simples y estáticos
DynamicData Datos de métodos, propiedades o campos Datos de prueba complejos o calculados
DataSource Archivos o bases de datos de datos externos Escenarios obsoletos con fuentes de datos externas

MSTest también proporciona los siguientes tipos para ampliar escenarios controlados por datos:

  • TestDataRow<T>: tipo de retorno para implementaciones de ITestDataSource (incluido DynamicData) que agrega soporte de metadatos, como nombres para mostrar, categorías e ignorar mensajes a casos de prueba individuales.
  • ITestDataSource: una interfaz que puede implementar en un atributo personalizado para crear atributos de origen de datos totalmente personalizados.

Sugerencia

Para las pruebas combinatorias (probar todas las combinaciones de varios conjuntos de parámetros), use el paquete NuGet Combinatorial.MSTest de código abierto. Este paquete mantenido por la comunidad está disponible en GitHub , pero Microsoft no lo mantiene.

DataRowAttribute

DataRowAttribute permite ejecutar el mismo método de prueba con varias entradas diferentes. Aplique uno o varios DataRow atributos a un método de prueba decorado con .TestMethodAttribute

El número y los tipos de argumentos deben coincidir exactamente con la signatura del método de prueba.

Sugerencia

Analizadores relacionados:

  • MSTEST0014 valida que DataRow los argumentos coinciden con la firma del método de prueba.
  • MSTEST0042 detecta entradas duplicadas DataRow que ejecutarían el mismo caso de prueba varias veces.

Uso básico

[TestClass]
public class CalculatorTests
{
    [TestMethod]
    [DataRow(1, 2, 3)]
    [DataRow(0, 0, 0)]
    [DataRow(-1, 1, 0)]
    [DataRow(100, 200, 300)]
    public void Add_ReturnsCorrectSum(int a, int b, int expected)
    {
        var calculator = new Calculator();
        Assert.AreEqual(expected, calculator.Add(a, b));
    }
}

Tipos de argumento admitidos

DataRow admite varios tipos de argumentos, incluidos primitivos, cadenas, matrices y valores NULL:

[TestClass]
public class DataRowExamples
{
    [TestMethod]
    [DataRow(1, "message", true, 2.0)]
    public void TestWithMixedTypes(int i, string s, bool b, float f)
    {
        // Test with different primitive types
    }

    [TestMethod]
    [DataRow(new string[] { "line1", "line2" })]
    public void TestWithArray(string[] lines)
    {
        Assert.AreEqual(2, lines.Length);
    }

    [TestMethod]
    [DataRow(null)]
    public void TestWithNull(object o)
    {
        Assert.IsNull(o);
    }

    [TestMethod]
    [DataRow(new string[] { "a", "b" }, new string[] { "c", "d" })]
    public void TestWithMultipleArrays(string[] input, string[] expected)
    {
        // Starting with MSTest v3, two arrays don't need wrapping
    }
}

Uso de parámetros para argumentos variables

Use la params palabra clave para aceptar un número variable de argumentos:

[TestClass]
public class ParamsExample
{
    [TestMethod]
    [DataRow(1, 2, 3, 4)]
    [DataRow(10, 20)]
    [DataRow(5)]
    public void TestWithParams(params int[] values)
    {
        Assert.IsTrue(values.Length > 0);
    }
}

Nombres de pantalla personalizados

Establezca la DisplayName propiedad para personalizar cómo aparecen los casos de prueba en el Explorador de pruebas:

[TestClass]
public class DisplayNameExample
{
    [TestMethod]
    [DataRow(1, 2, DisplayName = "Functional Case FC100.1")]
    [DataRow(3, 4, DisplayName = "Edge case: small numbers")]
    public void TestMethod(int i, int j)
    {
        Assert.IsTrue(i < j);
    }
}

Sugerencia

Para obtener más control sobre los metadatos de prueba, considere la posibilidad de usar TestDataRow<T> con DynamicData. TestDataRow<T> admite nombres para mostrar junto con categorías de prueba y omitir mensajes para casos de prueba individuales.

Omitir casos de prueba específicos

A partir de MSTest v3.8, use la IgnoreMessage propiedad para omitir filas de datos específicas:

[TestClass]
public class IgnoreDataRowExample
{
    [TestMethod]
    [DataRow(1, 2)]
    [DataRow(3, 4, IgnoreMessage = "Temporarily disabled - bug #123")]
    [DataRow(5, 6)]
    public void TestMethod(int i, int j)
    {
        // Only the first and third data rows run
        // The second is skipped with the provided message
    }
}

DynamicDataAttribute

DynamicDataAttribute permite proporcionar datos de prueba de métodos, propiedades o campos. Utilice este atributo cuando los datos de prueba sean complejos, calculados dinámicamente o demasiado detallados para atributos en línea DataRow.

Tipos de origen de datos admitidos

El origen de datos puede devolver cualquier IEnumerable<T> donde T sea uno de los tipos enumerados en la siguiente tabla. Cualquier colección que implemente IEnumerable<T> funciona, incluidas List<T>, matrices como T[], o tipos de colección personalizados. Elija según sus necesidades:

Tipo de valor devuelto Seguridad de tipos Compatibilidad con metadatos Más adecuado para
ValueTuple (por ejemplo, (int, string)) Tiempo de compilación No Mayoría de los escenarios: sintaxis simple con comprobación de tipos completa
Tuple<...> Tiempo de compilación No Cuando no se puede usar ValueTuple
TestDataRow<T> Tiempo de compilación Casos de prueba que necesitan nombres para mostrar, categorías o omitir mensajes
object[] Solo tiempo de ejecución No Código heredado: evite las nuevas pruebas.

Sugerencia

Para los nuevos métodos de datos de prueba, use ValueTuple para casos simples o TestDataRow<T> cuando necesite metadatos. Evite object[] porque carece de comprobación de tipos en tiempo de compilación y puede provocar errores en tiempo de ejecución a partir de errores de coincidencia de tipos.

Orígenes de datos

El origen de datos puede ser un método, una propiedad o un campo. Los tres son intercambiables: elija en función de su preferencia:

[TestClass]
public class DynamicDataExample
{
    // Method - best for computed or yielded data
    public static IEnumerable<(int Value, string Name)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }

    // Property - concise for static data
    public static IEnumerable<(int Value, string Name)> TestDataProperty =>
    [
        (1, "first"),
        (2, "second")
    ];

    // Field - simplest for static data
    public static IEnumerable<(int Value, string Name)> TestDataField =
    [
        (1, "first"),
        (2, "second")
    ];

    [TestMethod]
    [DynamicData(nameof(GetTestData))]
    public void TestWithMethod(int value, string name)
    {
        Assert.IsTrue(value > 0);
    }

    [TestMethod]
    [DynamicData(nameof(TestDataProperty))]
    public void TestWithProperty(int value, string name)
    {
        Assert.IsTrue(value > 0);
    }

    [TestMethod]
    [DynamicData(nameof(TestDataField))]
    public void TestWithField(int value, string name)
    {
        Assert.IsTrue(value > 0);
    }
}

Nota:

Los métodos, propiedades y campos del origen de datos deben ser public static y devolver un IEnumerable<T> de un tipo admitido.

Sugerencia

Analizador relacionado: MSTEST0018 valida que el origen de datos existe, es accesible y tiene la firma correcta.

Origen de datos de una clase diferente

Especifique una clase diferente mediante el parámetro type:

public class TestDataProvider
{
    public static IEnumerable<(int, string)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }
}

[TestClass]
public class DynamicDataExternalExample
{
    [TestMethod]
    [DynamicData(nameof(TestDataProvider.GetTestData), typeof(TestDataProvider))]
    public void TestMethod(int value1, string value2)
    {
        Assert.IsTrue(value1 > 0);
    }
}

Nombres de pantalla personalizados

Personalice los nombres para mostrar del caso de prueba mediante la DynamicDataDisplayName propiedad :

using System.Reflection;

[TestClass]
public class DynamicDataDisplayNameExample
{
    [TestMethod]
    [DynamicData(nameof(GetTestData), DynamicDataDisplayName = nameof(GetDisplayName))]
    public void TestMethod(int value1, string value2)
    {
        Assert.IsTrue(value1 > 0);
    }

    public static IEnumerable<(int, string)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }

    public static string GetDisplayName(MethodInfo methodInfo, object[] data)
    {
        return $"{methodInfo.Name} with value {data[0]} and '{data[1]}'";
    }
}

Nota:

El método de nombre para mostrar debe ser public static, devolver un stringy aceptar dos parámetros: MethodInfo y object[].

Sugerencia

Para obtener un enfoque más sencillo para los nombres para mostrar personalizados, considere la posibilidad de usar TestDataRow<T> con su DisplayName propiedad en lugar de un método independiente.

Omitir todos los casos de prueba de un origen de datos

A partir de MSTest v3.8, use IgnoreMessage para omitir todos los casos de prueba:

[TestClass]
public class IgnoreDynamicDataExample
{
    [TestMethod]
    [DynamicData(nameof(GetTestData), IgnoreMessage = "Feature not ready")]
    public void TestMethod(int value1, string value2)
    {
        // All test cases from GetTestData are skipped
    }

    public static IEnumerable<(int, string)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }
}

Sugerencia

Para omitir casos de prueba individuales, use TestDataRow<T> con la propiedad IgnoreMessage. Consulte la sección TestDataRow<T> .

TestDataRow

La TestDataRow<T> clase proporciona un control mejorado sobre los datos de prueba en pruebas controladas por datos. Use IEnumerable<T> como tipo de valor devuelto para el origen de datos TestDataRow<T> para especificar:

  • Nombres para mostrar personalizados: establezca un nombre para mostrar único por caso de prueba mediante la DisplayName propiedad .
  • Categorías de prueba: adjunte metadatos a casos de prueba individuales mediante la TestCategories propiedad .
  • Omitir mensajes: Omitir casos de prueba específicos por motivos mediante la IgnoreMessage propiedad.
  • Datos seguros para tipos: use genéricos para datos de prueba fuertemente tipados.

Uso básico

[TestClass]
public class TestDataRowExample
{
    [TestMethod]
    [DynamicData(nameof(GetTestDataRows))]
    public void TestMethod(int value1, string value2)
    {
        Assert.IsTrue(value1 > 0);
    }

    public static IEnumerable<TestDataRow<(int, string)>> GetTestDataRows()
    {
        yield return new TestDataRow<(int, string)>((1, "first"))
        {
            DisplayName = "Test Case 1: Basic scenario",
        };

        yield return new TestDataRow<(int, string)>((2, "second"))
        {
            DisplayName = "Test Case 2: Edge case",
            TestCategories = ["HighPriority", "Critical"],
        };

        yield return new TestDataRow<(int, string)>((3, "third"))
        {
            IgnoreMessage = "Not yet implemented",
        };
    }
}

DataSourceAttribute

Nota:

DataSource solo está disponible en .NET Framework. En el caso de los proyectos de .NET (Core), use DataRow o DynamicData en su lugar.

DataSourceAttribute Conecta pruebas a orígenes de datos externos, como archivos CSV, archivos XML o bases de datos.

Para obtener información detallada, consulte:

ITestDataSource

La ITestDataSource interfaz le permite crear atributos de origen de datos completamente personalizados. Implemente esta interfaz cuando necesite un comportamiento que no admitan los atributos integrados, como generar datos de prueba basados en variables de entorno, archivos de configuración u otras condiciones en tiempo de ejecución.

Miembros de interfaz

La interfaz define dos métodos:

Método Propósito
GetData(MethodInfo) Devuelve datos de prueba como IEnumerable<object?[]>
GetDisplayName(MethodInfo, object?[]?) Devuelve el nombre que se muestra de un caso de prueba.

Creación de un atributo de origen de datos personalizado

Para crear un origen de datos personalizado, defina una clase de atributo que herede de Attribute e implemente ITestDataSource:

using System.Globalization;
using System.Reflection;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MyDataSourceAttribute : Attribute, ITestDataSource
{
    public IEnumerable<object?[]> GetData(MethodInfo methodInfo)
    {
        // Return test data based on your custom logic
        yield return [1, "first"];
        yield return [2, "second"];
        yield return [3, "third"];
    }

    public string? GetDisplayName(MethodInfo methodInfo, object?[]? data)
    {
        return data is null
            ? null
            : string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, string.Join(",", data));
    }
}

[TestClass]
public class CustomDataSourceExample
{
    [TestMethod]
    [MyDataSource]
    public void TestWithCustomDataSource(int value, string name)
    {
        Assert.IsTrue(value > 0);
        Assert.IsNotNull(name);
    }
}

Ejemplo real: datos de prueba basados en entornos

En este ejemplo se muestra un atributo personalizado que genera datos de prueba basados en marcos de destino, filtrado en función del sistema operativo:

using System.Globalization;
using System.Reflection;

[AttributeUsage(AttributeTargets.Method)]
public class TargetFrameworkDataAttribute : Attribute, ITestDataSource
{
    private readonly string[] _frameworks;

    public TargetFrameworkDataAttribute(params string[] frameworks)
    {
        _frameworks = frameworks;
    }

    public IEnumerable<object?[]> GetData(MethodInfo methodInfo)
    {
        bool isWindows = OperatingSystem.IsWindows();

        foreach (string framework in _frameworks)
        {
            // Skip .NET Framework on non-Windows platforms
            if (!isWindows && framework.StartsWith("net4", StringComparison.Ordinal))
            {
                continue;
            }

            yield return [framework];
        }
    }

    public string? GetDisplayName(MethodInfo methodInfo, object?[]? data)
    {
        return data is null
            ? null
            : string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, data[0]);
    }
}

[TestClass]
public class CrossPlatformTests
{
    [TestMethod]
    [TargetFrameworkData("net48", "net8.0", "net9.0")]
    public void TestOnMultipleFrameworks(string targetFramework)
    {
        // Test runs once per applicable framework
        Assert.IsNotNull(targetFramework);
    }
}

Sugerencia

Para casos sencillos en los que solo tiene que personalizar nombres para mostrar o agregar metadatos, considere la posibilidad de usar TestDataRow<T> con DynamicData en lugar de implementar ITestDataSource. Reserve implementaciones personalizadas ITestDataSource para escenarios que requieran lógica de generación de datos compleja o filtrado dinámico.

Estrategia de despliegue

Los atributos de prueba controlados por datos admiten la TestDataSourceUnfoldingStrategy propiedad , que controla cómo aparecen los casos de prueba en el Explorador de pruebas y los resultados de TRX. Esta propiedad también determina si puede ejecutar casos de prueba individuales de forma independiente.

Fases de detección y ejecución

MSTest procesa pruebas controladas por datos en dos fases distintas:

  • Fase de detección: MSTest evalúa todos los atributos del origen de datos (DataRow, DynamicData, ITestDataSource) para determinar la lista de casos de prueba. Esta evaluación se produce antes de que se ejecuten los enlaces normales del ciclo de vida de MSTest,AssemblyInitializeClassInitialize , y otros métodos de instalación aún no se han ejecutado.
  • Fase de ejecución: MSTest ejecuta el ciclo de vida normal (inicialización del ensamblado, inicialización de clases, inicialización de pruebas, método de prueba, limpieza) y ejecuta cada caso de prueba con sus datos.

Dado que los orígenes de datos se evalúan durante la detección, el código de generación de datos no puede confiar en ningún estado configurado por AssemblyInitialize o ClassInitialize. Si el origen de datos depende de la lógica de configuración (por ejemplo, la lectura de una conexión de base de datos inicializada en ClassInitialize), se produce un error en la evaluación del origen de datos durante la detección.

Estrategias disponibles

Estrategia Comportamiento
Auto (valor predeterminado) MSTest determina la mejor estrategia de despliegue.
Unfold Todos los casos de prueba se expanden y se muestran individualmente.
Fold Todos los casos de prueba se agrupan en un único nodo de prueba.

Pruebas plegadas frente a pruebas desplegadas

La estrategia de desarrollo afecta a cómo se notifican los resultados de las pruebas en el Explorer de pruebas y la salida TRX:

  • Pruebas desplegadas: cada fila de datos aparece como una entrada de prueba independiente en el Explorer de pruebas y TRX. Puede ejecutar, depurar o filtrar casos de prueba individuales. Cada entrada tiene su propio estado de paso o error.
  • Pruebas plegadas: todas las filas de datos aparecen como un único nodo de prueba en el Explorador de pruebas. Una entrada aparece en TRX, con varios resultados asociados a ese único caso de prueba. No se pueden ejecutar ni filtrar filas de datos individuales de forma independiente.

La excepción durante la detección provoca el plegado

Cuando MSTest evalúa un origen de datos durante la detección y la evaluación produce una excepción, la prueba vuelve a un estado plegado independientemente de la estrategia de desarrollo configurada. Dado que el marco de trabajo no puede enumerar los casos de prueba individuales, registra el método de prueba como una sola entrada (plegada).

En tiempo de ejecución, MSTest vuelve a evaluar el origen de datos como parte del ciclo de vida normal. Si la excepción fue causada por un estado de configuración faltante (por ejemplo, una dependencia que ClassInitialize proporciona), la fuente de datos podría tener éxito durante la ejecución porque los ganchos del ciclo de vida se han ejecutado.

Nota:

Al depurar una prueba que lanza una excepción durante el descubrimiento, es posible que vea una excepción (por ejemplo, un NullReferenceException) antes de que comience la prueba. Esta excepción procede de la evaluación de la fase de detección. Presione Continuar en el depurador: la prueba se ejecuta normalmente porque la fase de ejecución ejecuta el ciclo de vida completo de MSTest, incluidos los métodos de inicialización. Para obtener más información, consulte microsoft/testfx#7774.

Cuándo cambiar la estrategia

En la mayoría de los escenarios, el comportamiento predeterminado Auto proporciona el mejor equilibrio. Considere la posibilidad de cambiar la estrategia de desarrollo cuando tenga requisitos específicos:

  • Use Fold si los orígenes de datos dependen del estado en tiempo de ejecución o de la lógica de configuración que no está disponible durante la detección.
  • Se usa Fold para orígenes de datos no deterministas que devuelven valores diferentes en cada evaluación.
  • Use Fold para reducir la sobrecarga cuando el rendimiento es un problema con un gran número de casos de prueba.

Ejemplo de uso

[TestClass]
public class UnfoldingExample
{
    [TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Unfold)] // That's the default behavior
    [DataRow(1, "one")]
    [DataRow(2, "two")]
    [DataRow(3, "three")]
    public void TestMethodWithUnfolding(int value, string text)
    {
        // Each test case appears individually in Test Explorer
    }

    [TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Fold)]
    [DataRow(1, "one")]
    [DataRow(2, "two")]
    [DataRow(3, "three")]
    public void TestMethodWithFolding(int value, string text)
    {
        // All test cases appear as a single collapsed node
    }
}

procedimientos recomendados

  • Elija el atributo correcto: use DataRow para datos simples y insertados. Se usa DynamicData para datos complejos o calculados.
  • Asigne un nombre a los casos de prueba: use DisplayName para facilitar la identificación de los errores de prueba.
  • Mantener los orígenes de datos cerca: defina orígenes de datos en la misma clase cuando sea posible para mejorar la capacidad de mantenimiento.
  • Usar datos significativos: elija los datos de prueba que ejercen casos perimetrales y condiciones de límite.
  • Considere la posibilidad de realizar pruebas combinatorias: para probar combinaciones de parámetros, use el paquete Combinatorial.MSTest .

Consulte también