Condividi tramite


Test basati sui dati in MSTest

I test basati sui dati consentono di eseguire lo stesso metodo di test con più set di dati di input. Anziché scrivere metodi di test separati per ogni test case, definire la logica di test una sola volta e fornire input diversi tramite attributi o origini dati esterne.

Informazioni generali

MSTest offre diversi attributi per i test basati sui dati:

Attribute Caso d'uso Ideale per
DataRow Dati di test inline Test case semplici e statici
DynamicData Dati provenienti da metodi, proprietà o campi Dati di test complessi o calcolati
TestDataRow<T> Dati avanzati con metadati Casi di test che necessitano di nomi visualizzati o categorie
DataSource File di dati esterni o database Scenari legacy con fonti di dati esterne
ITestDataSource Attributi dell'origine dati personalizzata Scenari completamente personalizzati basati sui dati

Suggerimento

Per i test combinatoriali (test di tutte le combinazioni di più set di parametri), usare il pacchetto NuGet Combinatorial.MSTest open source. Questo pacchetto gestito dalla community è disponibile in GitHub , ma non è gestito da Microsoft.

DataRowAttribute

DataRowAttribute consente di eseguire lo stesso metodo di test con più input diversi. Applicare uno o più DataRow attributi a un metodo di test e combinarlo con .TestMethodAttribute

Il numero e i tipi di argomenti devono corrispondere esattamente alla firma del metodo di test.

Suggerimento

Analizzatori correlati:

  • MSTEST0014 convalida che DataRow gli argomenti corrispondano alla firma del metodo di test.
  • MSTEST0042 rileva voci duplicate DataRow che potrebbero eseguire lo stesso caso di test più volte.

Utilizzo di base

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

Tipi di argomenti supportati

DataRow supporta vari tipi di argomento, tra cui primitive, stringhe, matrici e valori 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 di params per argomenti variabili

Usare la params parola chiave per accettare un numero variabile di argomenti:

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

Nomi visualizzati personalizzati

Impostare la proprietà DisplayName per personalizzare come appaiono i test case in Esplora test.

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

Suggerimento

Per un maggiore controllo sui metadati di test, provare a usare TestDataRow<T> con DynamicData. TestDataRow<T> supporta i nomi visualizzati insieme alle categorie di test e l'esclusione dei messaggi per i singoli casi di test.

Ignorare test case specifici

A partire da MSTest v3.8, usare la IgnoreMessage proprietà per ignorare righe di dati specifiche:

[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 Consente di fornire dati di test da metodi, proprietà o campi. Usare questo attributo quando i dati di test sono complessi, calcolati in modo dinamico o troppo dettagliato per gli attributi inline DataRow .

Tipi di origine dati supportati

L'origine dati può restituire qualsiasi IEnumerable<T> dove T è uno dei tipi elencati nella tabella seguente. Qualsiasi raccolta che implementa IEnumerable<T> funziona, inclusi List<T>, matrici come T[]o tipi di raccolta personalizzati. Scegliere in base alle proprie esigenze:

Tipo di ritorno Sicurezza dei tipi Supporto dei metadati Ideale per
ValueTuple (ad esempio, (int, string)) Fase di compilazione NO La maggior parte degli scenari: sintassi semplice con controllo completo dei tipi
Tuple<...> Fase di compilazione NO Quando non è possibile usare ValueTuple
TestDataRow<T> Tempo di compilazione Yes Casi di test che richiedono nomi di visualizzazione, categorie o messaggi da ignorare
object[] Solo runtime NO Codice legacy - da evitare per nuovi test

Suggerimento

Per i nuovi metodi di dati di test, usare ValueTuple per casi semplici o TestDataRow<T> quando sono necessari metadati. Evitare object[] perché manca il controllo dei tipi in fase di compilazione e può causare errori di runtime da mancate corrispondenze di tipo.

Origini dati

L'origine dati può essere un metodo, una proprietà o un campo. Tutti e tre sono intercambiabili: scegliere in base alle proprie preferenze:

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

Annotazioni

I metodi, le proprietà e i campi dell'origine dati devono essere public static e restituire un IEnumerable<T> oggetto di un tipo supportato.

Suggerimento

Analizzatore correlato: MSTEST0018 verifica che l'origine dati esista, sia accessibile e abbia la firma corretta.

Fonte dati di una classe diversa

Specificare una classe diversa usando il parametro di tipo :

public class TestDataProvider
{
    public static IEnumerable<object[]> GetTestData()
    {
        yield return new object[] { 1, "first" };
        yield return new object[] { 2, "second" };
    }
}

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

Nomi visualizzati personalizzati

Personalizzare i nomi visualizzati del test case usando la DynamicDataDisplayName proprietà :

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<object[]> GetTestData()
    {
        yield return new object[] { 1, "first" };
        yield return new object[] { 2, "second" };
    }

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

Annotazioni

Il metodo del nome visualizzato deve essere public static, restituire un stringe accettare due parametri: MethodInfo e object[].

Suggerimento

Per un approccio più semplice ai nomi visualizzati personalizzati, è consigliabile usare TestDataRow<T> con la relativa DisplayName proprietà anziché un metodo separato.

Ignorare tutti i casi di test da un'origine dati

A partire da MSTest v3.8, usare IgnoreMessage per ignorare tutti i test case:

[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<object[]> GetTestData()
    {
        yield return new object[] { 1, "first" };
        yield return new object[] { 2, "second" };
    }
}

Suggerimento

Per ignorare i singoli test case, usare TestDataRow<T> con la relativa IgnoreMessage proprietà . Vedere la sezione TestDataRow<T> .

TestDataRow

La TestDataRow<T> classe fornisce un controllo avanzato sui dati di test nei test basati sui dati. Usare IEnumerable<TestDataRow<T>> come tipo di ritorno dell'origine dati per specificare:

  • Nomi visualizzati personalizzati: impostare un nome visualizzato univoco per ogni test case
  • Categorie di test: Allegare metadati a singoli test case
  • Ignora messaggi: Salta test case specifici fornendo motivazioni
  • Dati sicuri rispetto ai tipi: usare generics per i dati di test fortemente tipizzati

Utilizzo di base

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

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

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

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

DataSourceAttribute

Annotazioni

DataSource è disponibile solo in .NET Framework. Per i progetti .NET (Core), usare DataRow o DynamicData .

DataSourceAttribute Connette i test a origini dati esterne, ad esempio file CSV, file XML o database.

Per informazioni dettagliate, vedere:

ITestDataSource

L'interfaccia ITestDataSource consente di creare attributi di origine dati completamente personalizzati. Implementare questa interfaccia quando è necessario un comportamento che gli attributi predefiniti non supportano, ad esempio la generazione di dati di test in base a variabili di ambiente, file di configurazione o altre condizioni di runtime.

Membri di interfaccia

L'interfaccia definisce due metodi:

Metodo Scopo
GetData(MethodInfo) Restituisce i dati di test come IEnumerable<object?[]>
GetDisplayName(MethodInfo, object?[]?) Restituisce il nome visualizzato per un test case

Creazione di un attributo di origine dati personalizzato

Per creare un'origine dati personalizzata, definire una classe di attributi che eredita da Attribute e implementa 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);
    }
}

Esempio reale: dati di test basati sull'ambiente

Questo esempio mostra un attributo personalizzato che genera dati di test basati su framework di destinazione, filtrando in base al 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);
    }
}

Suggerimento

Per semplici casi in cui è sufficiente personalizzare i nomi visualizzati o aggiungere metadati, è consigliabile usare TestDataRow<T> con DynamicData anziché implementare ITestDataSource. Riservare implementazioni personalizzate ITestDataSource per scenari che richiedono filtri dinamici o logica di generazione di dati complessa.

Strategia di dispiegamento

Gli attributi di test basati sui dati supportano la TestDataSourceUnfoldingStrategy proprietà , che controlla la modalità di visualizzazione dei test case nei risultati di Esplora test e TRX. Questa proprietà determina anche se è possibile eseguire singoli test case in modo indipendente.

Strategie disponibili

Strategia Comportamento
Auto (impostazione predefinita) MSTest determina la strategia migliore
Unfold Tutti i test case vengono espansi e visualizzati singolarmente
Fold Tutti i test case vengono compressi in un singolo nodo di test

Quando modificare la strategia

Per la maggior parte degli scenari, il comportamento predefinito Auto offre il miglior equilibrio. Prendere in considerazione la modifica di questa impostazione solo quando si hanno requisiti specifici:

  • Origini dati non deterministiche
  • Limitazioni o bug noti in MSTest
  • Problemi di prestazione con molti test case

Esempio di utilizzo

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

    [TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Fold)]
    [DynamicData(nameof(GetData))]
    public void TestMethodWithFolding(int value, string text)
    {
        // All test cases appear as a single collapsed node
    }

    public static IEnumerable<(int, string)> GetData()
    {
        yield return (1, "one");
        yield return (2, "two");
        yield return (3, "three");
    }
}

Procedure consigliate

  1. Scegliere l'attributo corretto: usare DataRow per dati semplici e inline. Usare DynamicData per dati complessi o calcolati.

  2. Assegnare un nome ai test case: usare DisplayName per semplificare l'identificazione degli errori di test.

  3. Mantenere vicine le origini dati: definire le origini dati nella stessa classe quando possibile per una migliore gestibilità.

  4. Usare dati significativi: scegliere i dati di test che esercitano casi limite e condizioni limite.

  5. Considerare i test combinatori: per testare le combinazioni di parametri, usare il pacchetto Combinatorial.MSTest .

Vedere anche