Freigeben über


Datengesteuerte Tests in MSTest

Mit datengesteuerten Tests können Sie dieselbe Testmethode mit mehreren Eingabedatensätzen ausführen. Anstatt separate Testmethoden für jeden Testfall zu schreiben, definieren Sie ihre Testlogik einmal, und stellen Sie verschiedene Eingaben über Attribute oder externe Datenquellen bereit.

Überblick

MSTest bietet mehrere Attribute für datengesteuerte Tests:

Merkmal Anwendungsfall Am besten geeignet für:
DataRow Inlinetestdaten Einfache, statische Testfälle
DynamicData Daten aus Methoden, Eigenschaften oder Feldern Komplexe oder berechnete Testdaten
TestDataRow<T> Erweiterte Daten mit Metadaten Testfälle, die Anzeigenamen oder Kategorien benötigen
DataSource Externe Datendateien oder Datenbanken Altsystem-Szenarien mit externen Datenquellen
ITestDataSource Benutzerdefinierte Datenquellenattribute Vollständig benutzerdefinierte datengesteuerte Szenarien

Tipp

Verwenden Sie zum Kombinieren von Tests (testen aller Kombinationen mehrerer Parametersätze) das Open-Source-Paket Combinatorial.MSTest NuGet. Dieses von der Community verwaltete Paket ist auf GitHub verfügbar , wird jedoch nicht von Microsoft verwaltet.

DataRowAttribute

Auf DataRowAttribute diese Weise können Sie dieselbe Testmethode mit mehreren verschiedenen Eingaben ausführen. Wenden Sie ein oder mehrere DataRow Attribute auf eine Testmethode an, und kombinieren Sie es mit der TestMethodAttribute.

Die Anzahl von Argumenten und die Argumenttypen müssen genau mit der Testmethodensignatur übereinstimmen.

Tipp

Verwandte Analysegeräte:

  • MSTEST0014 überprüft, ob DataRow Argumente der Signatur der Testmethode entsprechen.
  • MSTEST0042 erkennt doppelte DataRow Einträge, die denselben Testfall mehrmals ausführen würden.

Grundlegende Nutzung

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

Unterstützte Argumenttypen

DataRow unterstützt verschiedene Argumenttypen wie Grundtypen, Zeichenfolgen, Arrays und Nullwerte:

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

Verwenden von Params für variablen Argumente

Verwenden Sie das params Schlüsselwort, um eine variable Anzahl von Argumenten zu akzeptieren:

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

Benutzerdefinierte Anzeigenamen

Legen Sie die DisplayName Eigenschaft fest, um anzupassen, wie Testfälle im Test-Explorer angezeigt werden:

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

Tipp

Um mehr Kontrolle über Testmetadaten zu haben, erwägen Sie die Verwendung von TestDataRow<T> mit DynamicData. TestDataRow<T> unterstützt Anzeigenamen zusammen mit Testkategorien und das Ignorieren von Meldungen für die einzelnen Testfälle.

Ignorieren bestimmter Testfälle

Verwenden Sie ab MSTest v3.8 die IgnoreMessage Eigenschaft, um bestimmte Datenzeilen zu überspringen:

[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

Auf diese DynamicDataAttribute Weise können Sie Testdaten aus Methoden, Eigenschaften oder Feldern bereitstellen. Verwenden Sie dieses Attribut, wenn Testdaten komplex, dynamisch berechnet oder zu ausführlich für Inlineattribute DataRow sind.

Unterstützte Datenquellentypen

Die Datenquelle kann jedes IEnumerable<T> zurückgeben, wobei T einer der in der folgenden Tabelle aufgeführten Typen ist. Jede Sammlung, die IEnumerable<T> implementiert, funktioniert, einschließlich List<T>, Arrays wie T[] oder benutzerdefinierter Sammlungstypen. Wählen Sie basierend auf Ihren Anforderungen aus:

Rückgabetyp Typsicherheit Metadatenunterstützung Am besten geeignet für:
ValueTuple (z. B. (int, string)) Kompilierungszeit Nein Die meisten Szenarien – einfache Syntax mit vollständiger Typüberprüfung
Tuple<...> Kompilierungszeit Nein Wenn Sie ValueTuple nicht verwenden können
TestDataRow<T> Kompilierungszeit Yes Testfälle, die Anzeigenamen, Kategorien oder Ignorieren von Nachrichten benötigen
object[] Nur Laufzeit Nein Legacy-Code – für neue Tests vermeiden

Tipp

Verwenden Sie ValueTuple für einfache Fälle oder TestDataRow<T>, wenn Metadaten benötigt werden, bei neuen Testdatenmethoden. Vermeiden Sie object[], da die Typüberprüfung zur Kompilierzeit fehlt und Laufzeitfehler aufgrund von Typinkompatibilitäten auftreten können.

Datenquellen

Die Datenquelle kann eine Methode, Eigenschaft oder ein Feld sein. Alle drei sind austauschbar – wählen Sie basierend auf Ihrer Einstellung aus:

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

Hinweis

Datenquellenmethoden, Eigenschaften und Felder müssen vom Typ public static sein und einen IEnumerable<T> eines unterstützten Typs zurückgeben.

Tipp

Verwandte Analyse: MSTEST0018 überprüft, ob die Datenquelle vorhanden ist, zugänglich ist und über die richtige Signatur verfügt.

Datenquelle aus einer anderen Klasse

Geben Sie eine andere Klasse mit dem Typparameter an:

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

Benutzerdefinierte Anzeigenamen

Anpassen der Anzeigenamen von Testfällen mithilfe der DynamicDataDisplayName Eigenschaft:

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]}'";
    }
}

Hinweis

Die Methode für den Anzeigenamen muss ein public static zurückgeben, ein string und zwei Parameter akzeptieren: MethodInfo und object[].

Tipp

Für einen einfacheren Ansatz für benutzerdefinierte Anzeigenamen sollten Sie TestDataRow<T> mit seiner DisplayName Eigenschaft anstelle einer separaten Methode verwenden.

Ignorieren aller Testfälle aus einer Datenquelle

Ab MSTest v3.8 können Sie alle Testfälle mit Hilfe von IgnoreMessage überspringen.

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

Tipp

Verwenden Sie TestDataRow<T> zusammen mit seiner Eigenschaft IgnoreMessage, um einzelne Testfälle zu ignorieren. Weitere Informationen finden Sie im Abschnitt "TestDataRow<T> ".

TestDataRow

Die TestDataRow<T> Klasse bietet erweiterte Kontrolle über Testdaten in datengesteuerten Tests. Verwenden Sie IEnumerable<TestDataRow<T>> den Rückgabetyp der Datenquelle, um Folgendes anzugeben:

  • Benutzerdefinierte Anzeigenamen: Festlegen eines eindeutigen Anzeigenamens pro Testfall
  • Testkategorien: Anfügen von Metadaten an einzelne Testfälle
  • Ignorieren von Nachrichten: Überspringen bestimmter Testfälle mit Gründen
  • Typsichere Daten: Verwenden von Generika für stark typierte Testdaten

Grundlegende Nutzung

[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

Hinweis

DataSource ist nur in .NET Framework verfügbar. Verwenden Sie stattdessen DataRow oder DynamicData für .NET (Core)-Projekte.

Der DataSourceAttribute verbindet Tests mit externen Datenquellen wie CSV-Dateien, XML-Dateien oder Datenbanken.

Ausführliche Informationen finden Sie unter:

ITestDataSource

Über die ITestDataSource Schnittstelle können Sie vollständig benutzerdefinierte Datenquellenattribute erstellen. Implementieren Sie diese Schnittstelle, wenn Sie Verhalten benötigen, das die integrierten Attribute nicht unterstützen, z. B. das Generieren von Testdaten basierend auf Umgebungsvariablen, Konfigurationsdateien oder anderen Laufzeitbedingungen.

Interface members (Schnittstellenmember)

Die Schnittstelle definiert zwei Methoden:

Methode Zweck
GetData(MethodInfo) Gibt Testdaten als IEnumerable<object?[]>
GetDisplayName(MethodInfo, object?[]?) Gibt den Anzeigenamen für einen Testfall zurück.

Erstellen eines benutzerdefinierten Datenquellen-Attributs

Um eine benutzerdefinierte Datenquelle zu erstellen, definieren Sie eine Attributklasse, die von Attribute erbt und ITestDataSource implementiert.

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

Reales Beispiel: Umgebungsbasierte Testdaten

Dieses Beispiel zeigt ein benutzerdefiniertes Attribut, das Testdaten basierend auf Zielframeworks generiert und basierend auf dem Betriebssystem gefiltert wird:

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

Tipp

Für einfache Fälle, in denen Sie nur Anzeigenamen anpassen oder Metadaten hinzufügen müssen, sollten Sie TestDataRow<T> anstelle DynamicData der Implementierung ITestDataSourceverwenden. Reservieren Sie benutzerdefinierte ITestDataSource Implementierungen für Szenarien, die dynamische Filterung oder komplexe Logik für die Datengenerierung erfordern.

Entwicklungsstrategie

Datengesteuerte Testattribute unterstützen die TestDataSourceUnfoldingStrategy Eigenschaft, die steuert, wie Testfälle im Test-Explorer und TRX-Ergebnissen angezeigt werden. Diese Eigenschaft bestimmt auch, ob Einzelne Testfälle unabhängig voneinander ausgeführt werden können.

Verfügbare Strategien

Strategie Verhalten
Auto (Standardwert) MSTest bestimmt, dass sich die beste Strategie entwickelt
Unfold Alle Testfälle werden erweitert und einzeln angezeigt.
Fold Alle Testfälle werden in einen einzigen Testknoten reduziert.

Wann die Strategie geändert werden soll

Für die meisten Szenarien bietet das Standardverhalten Auto das beste Gleichgewicht. Erwägen Sie, diese Einstellung nur zu ändern, wenn Sie bestimmte Anforderungen haben:

  • Nicht deterministische Datenquellen
  • Bekannte Einschränkungen oder Fehler in MSTest
  • Leistungsbedenken bei vielen Testfällen

Anwendungsbeispiel

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

Bewährte Methoden

  1. Wählen Sie das richtige Attribut aus: Wird für einfache Inlinedaten verwendet DataRow . Verwenden Sie DynamicData für komplexe oder berechnete Daten.

  2. Benennen Sie Ihre Testfälle: Verwenden Sie die Verwendung DisplayName , um Testfehler einfacher zu identifizieren.

  3. Schließen Sie Datenquellen: Definieren Sie Datenquellen in derselben Klasse, wenn möglich, um eine bessere Wartung zu ermöglichen.

  4. Verwenden Sie aussagekräftige Daten: Wählen Sie Testdaten aus, die Randfälle und Grenzbedingungen üben.

  5. Erwägen Sie kombinatorische Tests: Verwenden Sie zum Testen von Parameterkombinationen das Paket Combinatorial.MSTest .

Siehe auch