Partage via


Tests pilotés par les données dans MSTest

Les tests pilotés par les données vous permettent d’exécuter la même méthode de test avec plusieurs jeux de données d’entrée. Au lieu d’écrire des méthodes de test distinctes pour chaque cas de test, définissez votre logique de test une fois et fournissez des entrées différentes via des attributs ou des sources de données externes.

Aperçu

MSTest fournit plusieurs attributs pour les tests pilotés par les données :

Caractéristique Cas d’utilisation Idéal pour
DataRow Données de test en ligne Cas de test statiques simples
DynamicData Données provenant de méthodes, de propriétés ou de champs Données de test complexes ou calculées
DataSource Fichiers ou bases de données de données externes Scénarios hérités avec des sources de données externes

MSTest fournit également les types suivants pour étendre les scénarios pilotés par les données :

  • TestDataRow<T>: type de retour pour les implémentations ITestDataSource (y compris DynamicData) qui ajoutent la prise en charge des métadonnées telles que les noms d’affichage, les catégories et l'ignorance des messages à des cas de test individuels.
  • ITestDataSource: interface que vous pouvez implémenter sur un attribut personnalisé pour créer des attributs de source de données entièrement personnalisés.

Conseil / Astuce

Pour les tests combinatorials (test de toutes les combinaisons de plusieurs jeux de paramètres), utilisez le package NuGet Combinatorial.MSTest open source. Ce package géré par la communauté est disponible sur GitHub , mais n’est pas géré par Microsoft.

DataRowAttribute

La DataRowAttribute vous permet d'exécuter la même méthode de test avec plusieurs entrées différentes. Appliquez un ou plusieurs DataRow attributs à une méthode de test et combinez-le avec le TestMethodAttribute.

Le nombre et les types d’arguments doivent correspondre exactement à la signature de la méthode de test.

Conseil / Astuce

Analyseurs associés :

  • MSTEST0014 valide que DataRow les arguments correspondent à la signature de méthode de test.
  • MSTEST0042 détecte les entrées en double DataRow qui exécuteraient le même cas de test plusieurs fois.

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

Types d’arguments pris en charge

DataRow prend en charge différents types d’arguments, notamment les primitives, les chaînes, les tableaux et les valeurs 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
    }
}

Utilisation de params pour les arguments de variable

Utilisez le params mot clé pour accepter un nombre variable d’arguments :

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

Noms d’affichage personnalisés

Définissez la propriété pour personnaliser l’affichage DisplayName des cas de test dans l’Explorateur de tests :

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

Conseil / Astuce

Pour plus de contrôle sur les métadonnées de test, envisagez d’utiliser TestDataRow<T> avec DynamicData. TestDataRow<T> prend en charge les noms d’affichage, ainsi que les catégories de test et ignore les messages pour les cas de test individuels.

Ignorer des cas de test spécifiques

À compter de MSTest v3.8, utilisez la IgnoreMessage propriété pour ignorer des lignes de données spécifiques :

[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

Le DynamicDataAttribute permet de fournir des données de test à partir de méthodes, de propriétés ou de champs. Utilisez cet attribut lorsque les données de test sont complexes, calculées dynamiquement ou trop détaillées pour les attributs inline DataRow .

Types de sources de données pris en charge

La source de données peut retourner n’importe quel IEnumerable<T>T est l'un des types répertoriés dans le tableau suivant. Toute collection implémentant IEnumerable<T> fonctionne, y compris List<T>, des tableaux tels que T[], ou des types de collection personnalisés. Choisissez en fonction de vos besoins :

Type de retour Sécurité des types Prise en charge des métadonnées Idéal pour
ValueTuple (par exemple, (int, string)) Temps de compilation Non La plupart des scénarios - syntaxe simple avec vérification de type complète
Tuple<...> Temps de compilation Non Quand vous ne pouvez pas utiliser ValueTuple
TestDataRow<T> Temps de compilation Oui Cas de test nécessitant des noms d’affichage, des catégories ou des messages à ignorer
object[] Runtime uniquement Non Code hérité : éviter les nouveaux tests

Conseil / Astuce

Pour les nouvelles méthodes de données de test, utilisez-les ValueTuple pour des cas simples ou TestDataRow<T> lorsque vous avez besoin de métadonnées. Évitez object[] parce qu’il manque de vérification de type au moment de la compilation et peut entraîner des erreurs d’exécution à partir d’incompatibilités de type.

Sources de données

La source de données peut être une méthode, une propriété ou un champ. Les trois sont interchangeables : choisissez en fonction de vos préférences :

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

Note

Les méthodes, propriétés et champs des sources de données doivent être public static et retourner un IEnumerable<T> d'un type pris en charge.

Conseil / Astuce

Analyseur associé : MSTEST0018 vérifie que la source de données existe, est accessible et a la signature correcte.

Source de données à partir d’une autre classe

Spécifiez une classe différente à l’aide du paramètre de 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);
    }
}

Noms d’affichage personnalisés

Personnalisez les noms d'affichage des cas de test à l’aide de la DynamicDataDisplayName propriété :

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

Note

La méthode de nom d’affichage doit être public static, retourner un string, et accepter deux paramètres : MethodInfo et object[].

Conseil / Astuce

Pour une approche plus simple des noms d’affichage personnalisés, envisagez d’utiliser TestDataRow<T> avec sa DisplayName propriété au lieu d’une méthode distincte.

Ignorer tous les cas de test d’une source de données

À compter de MSTest v3.8, utilisez cette fonction IgnoreMessage pour ignorer tous les cas de test :

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

Conseil / Astuce

Pour ignorer les cas de test individuels, utilisez TestDataRow<T> avec sa propriété IgnoreMessage. Consultez la section TestDataRow<>.

TestDataRow

La TestDataRow<T> classe fournit un contrôle amélioré sur les données de test dans les tests pilotés par les données. Utilisez IEnumerable<T> comme type de retour de source de données pour spécifier :

  • Noms d’affichage personnalisés : définissez un nom d’affichage unique par cas de test à l’aide de la DisplayName propriété.
  • Catégories de test : attacher des métadonnées à des cas de test individuels à l’aide de la TestCategories propriété.
  • Ignorer les messages : ignorez des cas de test spécifiques avec des raisons d’utilisation de la IgnoreMessage propriété.
  • Données de type sécurisé : utilisez des génériques pour les données de test fortement typées.

Utilisation de base

[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

Note

DataSource est disponible uniquement dans .NET Framework. Pour les projets .NET (Core), utilisez plutôt DataRow ou DynamicData.

Les DataSourceAttribute tests se connectent à des sources de données externes telles que des fichiers CSV, des fichiers XML ou des bases de données.

Pour plus d'informations, consultez :

ITestDataSource

L’interface ITestDataSource vous permet de créer des attributs de source de données complètement personnalisés. Implémentez cette interface lorsque vous avez besoin d’un comportement que les attributs intégrés ne prennent pas en charge, comme la génération de données de test basées sur des variables d’environnement, des fichiers de configuration ou d’autres conditions d’exécution.

Membres d’interfaces

L’interface définit deux méthodes :

Méthode Objectif
GetData(MethodInfo) Retourne des données de test en tant que IEnumerable<object?[]>
GetDisplayName(MethodInfo, object?[]?) Retourne le nom d'affichage d’un cas de test

Création d’un attribut de source de données personnalisé

Pour créer une source de données personnalisée, définissez une classe d’attributs qui hérite de Attribute et implémente 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);
    }
}

Exemple réel : données de test basées sur l’environnement

Cet exemple montre un attribut personnalisé qui génère des données de test basées sur des frameworks cibles, le filtrage basé sur le système d’exploitation :

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

Conseil / Astuce

Pour les cas simples où vous devez simplement personnaliser des noms d’affichage ou ajouter des métadonnées, envisagez d’utiliser TestDataRow<T> au DynamicData lieu d’implémenter ITestDataSource. Réservez des implémentations personnalisées ITestDataSource pour les scénarios nécessitant un filtrage dynamique ou une logique de génération de données complexe.

Stratégie de déploiement

Les attributs de test pilotés par les données prennent en charge la TestDataSourceUnfoldingStrategy propriété, qui contrôle la façon dont les cas de test s’affichent dans l’Explorateur de tests et les résultats TRX. Cette propriété détermine également si vous pouvez exécuter des cas de test individuels indépendamment.

Stratégies disponibles

Stratégie Comportement
Auto (valeur par défaut) MSTest détermine la meilleure manière de développer une stratégie
Unfold Tous les cas de test sont développés et affichés individuellement
Fold Tous les cas de test sont réduits en un seul nœud de test

Quand modifier la stratégie

Pour la plupart des scénarios, le comportement par défaut Auto fournit le meilleur équilibre. Envisagez de modifier ce paramètre uniquement lorsque vous avez des exigences spécifiques :

  • Sources de données non déterministes
  • Limitations ou bogues connus dans MSTest
  • Problèmes de performances avec de nombreux cas de test

Exemple d’utilisation

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

Meilleures pratiques

  1. Choisissez l’attribut approprié : utiliser DataRow pour des données simples et inline. Utiliser DynamicData pour les données complexes ou calculées.

  2. Nommez vos cas de test : utilisez cette méthode DisplayName pour faciliter l’identification des échecs de test.

  3. Fermer les sources de données : définissez les sources de données dans la même classe si possible pour une meilleure facilité de maintenance.

  4. Utilisez des données significatives : choisissez des données de test qui exercicent des cas de périphérie et des conditions de limite.

  5. Envisagez le test combinatorial : pour tester des combinaisons de paramètres, utilisez le package Combinatorial.MSTest .

Voir aussi