Créer un test unitaire piloté par les données

Vous pouvez utiliser l’infrastructure de test unitaire de Microsoft (MSTest) pour le code managé afin de configurer une méthode de test unitaire permettant de récupérer des valeurs auprès d’une source de données. La méthode est exécutée successivement pour chaque ligne de la source de données, ce qui permet de tester facilement diverses entrées à l’aide d’une seule méthode.

Un test unitaire piloté par les données peut utiliser l’un des types suivants :

  • données inline à l’aide de l’attribut DataRow
  • données membres à l’aide de l’attribut DynamicData
  • à partir d’un fournisseur de source connu à l’aide de l’attribut DataSource

Méthode testée

Par exemple, vous avez :

  1. Une solution appelée MyBank, qui accepte et traite les transactions de différents types de compte.

  2. Un projet dans MyBank, appelé BankDb, qui gère les transactions des comptes.

  3. Une classe appelée Maths dans le projet BankDb, qui remplit les fonctions mathématiques permettant de vérifier que les transactions sont avantageuses pour la banque.

  4. Un projet de test unitaire appelé BankDbTests pour tester le comportement du composant BankDb.

  5. Une classe de test unitaire appelée MathsTests pour vérifier le comportement de la classe Maths.

Nous allons tester une méthode dans Maths qui additionne deux entiers en utilisant boucle :

public int AddIntegers(int first, int second)
{
    int sum = first;
    for (int i = 0; i < second; i++)
    {
        sum += 1;
    }

    return sum;
}

Tester la méthode de test

Test piloté par les données inline

Pour les tests inline, MSTest utilise DataRow pour spécifier les valeurs utilisées par le test piloté par les données. Le test de cet exemple s’exécute successivement pour chaque ligne de données.

[TestMethod]
[DataRow(1, 1, 2)]
[DataRow(2, 2, 4)]
[DataRow(3, 3, 6)]
[DataRow(0, 0, 1)] // The test run with this row fails
public void AddIntegers_FromDataRowTest(int x, int y, int expected)
{
    var target = new Maths();
    int actual = target.AddIntegers(x, y);
    Assert.AreEqual(expected, actual,
        "x:<{0}> y:<{1}>",
        new object[] {x, y});
}

Test piloté par les données des membres

MSTest utilise l’attribut DynamicData pour spécifier le nom, le type (propriété, valeur par défaut ou méthode) et le type de définition (le type actuel est utilisé par défaut) du membre qui fournit les données utilisées par le test piloté par les données.

public static IEnumerable<object[]> AdditionData
{
    get
    {
        return new[]
        { 
            new object[] { 1, 1, 2 },
            new object[] { 2, 2, 4 },
            new object[] { 3, 3, 6 },
            new object[] { 0, 0, 1 }, // The test run with this row fails
        };
    }
}

[TestMethod]
[DynamicData(nameof(AdditionData))]
public void AddIntegers_FromDynamicDataTest(int x, int y, int expected)
{
    var target = new Maths();
    int actual = target.AddIntegers(x, y);
    Assert.AreEqual(expected, actual,
        "x:<{0}> y:<{1}>",
        new object[] {x, y});
}

Il est également possible d’écraser le nom d’affichage généré par défaut à l’aide de la propriété DynamicDataDisplayName de l’attribut DynamicData. La signature de la méthode de nom d’affichage doit être public static string et accepter deux paramètres, le premier de type MethodInfo et le second de type object[].

public static string GetCustomDynamicDataDisplayName(MethodInfo methodInfo, object[] data)
{
    return string.Format("DynamicDataTestMethod {0} with {1} parameters", methodInfo.Name, data.Length);
}

[DynamicData(nameof(AdditionData), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))]

Test piloté par les données du fournisseur de source

La création d’un test unitaire piloté par les sources de données comprend les étapes suivantes :

  1. Créez une source de données qui contient les valeurs que vous utilisez dans la méthode de test. La source de données peut correspondre à n’importe quel type inscrit sur la machine qui exécute le test.

  2. Ajoutez une propriété TestContext publique de type TestContext à la classe de test.

  3. Créer une méthode de test unitaire

  4. Ajoutez-y un attribut DataSourceAttribute.

  5. Utilisez la propriété d’indexeur DataRow pour récupérer les valeurs que vous utilisez dans un test.

Création d'une source de données

Pour tester la méthode AddIntegers, créez une source de données qui spécifie une plage de valeurs pour les paramètres et la somme à retourner. Dans cet exemple, nous allons créer une base de données SQL Compact nommée MathsData et une table nommée AddIntegersData, qui contient les noms et valeurs de colonnes suivants

FirstNumber SecondNumber SUM
0 1 1
1 1 2
2 -3 -1

Ajouter un objet TestContext à la classe de test

Le framework de tests unitaires crée un objet TestContext pour stocker les informations de source de données d’un test piloté par les données. Le framework définit ensuite cet objet en tant que valeur de la propriété TestContext que vous créez.

public TestContext TestContext { get; set; }

Dans votre méthode de test, vous accédez aux données via la propriété d’indexeur DataRow de TestContext.

Notes

.NET Core ne prend pas en charge l’attribut DataSource. Si vous essayez d’accéder aux données de test de cette manière dans un projet de test unitaire .NET Core, UWP ou WinUI, une erreur de type « "TestContext" ne contient pas de définition pour "DataRow" et aucune méthode d’extension accessible "DataRow" acceptant un premier argument de type "TestContext" n’a été localisée (une directive using ou une référence d’assembly est-elle manquante ?) » se produit.

Écrire la méthode de test

La méthode de test de AddIntegers est relativement simple. Pour chaque ligne de la source de données, appelez AddIntegers avec les valeurs de colonne FirstNumber et SecondNumber en tant que paramètres, puis vérifiez la valeur retournée par rapport à la valeur de colonne Sum :

[TestMethod]
[DataSource(@"Provider=Microsoft.SqlServerCe.Client.4.0; Data Source=C:\Data\MathsData.sdf;", "Numbers")]
public void AddIntegers_FromDataSourceTest()
{
    var target = new Maths();

    // Access the data
    int x = Convert.ToInt32(TestContext.DataRow["FirstNumber"]);
    int y = Convert.ToInt32(TestContext.DataRow["SecondNumber"]);
    int expected = Convert.ToInt32(TestContext.DataRow["Sum"]);
    int actual = target.AddIntegers(x, y);
    Assert.AreEqual(expected, actual,
        "x:<{0}> y:<{1}>",
        new object[] {x, y});
}

Spécifier DataSourceAttribute

L’attribut DataSource spécifie la chaîne de connexion de la source de données et le nom de la table que vous utilisez dans la méthode de test. Les informations exactes de la chaîne de connexion varient selon le genre de source de données que vous utilisez. Dans cet exemple, nous avons utilisé une base de données SqlServerCe.

[DataSource(@"Provider=Microsoft.SqlServerCe.Client.4.0;Data Source=C:\Data\MathsData.sdf", "AddIntegersData")]

Attention

La chaîne de connexion peut contenir des données sensibles (par exemple, un mot de passe). La chaîne de connexion est stockée en texte brut dans le code source et dans l’assembly compilé. Restreignez l’accès au code source et à l’assembly pour protéger ces informations sensibles.

L’attribut DataSource a trois constructeurs.

[DataSource(dataSourceSettingName)]

Un constructeur avec un seul paramètre utilise les informations de connexion stockées dans le fichier app.config de la solution. dataSourceSettingsName est le nom de l’élément XML contenu dans le fichier config, qui spécifie les informations de connexion.

L’utilisation d’un fichier app.config vous permet de changer l’emplacement de la source de données sans changer le test unitaire lui-même. Pour plus d’informations sur la création et l’utilisation d’un fichier app.config, consultez Procédure pas à pas : utilisation d’un fichier de configuration pour définir une source de données

[DataSource(connectionString, tableName)]

Le constructeur DataSource avec deux paramètres spécifie la chaîne de connexion de la source de données et le nom de la table qui contient les données de la méthode de test.

Les chaînes de connexion dépendent du type de la source de données. Toutefois, elles doivent contenir un élément Provider qui spécifie le nom invariant du fournisseur de données.

[DataSource(
    dataProvider,
    connectionString,
    tableName,
    dataAccessMethod
    )]

Utiliser TestContext.DataRow pour accéder aux données

Pour accéder aux données de la table AddIntegersData, utilisez l’indexeur TestContext.DataRow. Comme DataRow est un objet DataRow, récupérez les valeurs de colonne par noms d’index ou de colonnes. Dans la mesure où les valeurs sont retournées en tant qu’objets, convertissez-les vers le type approprié :

int x = Convert.ToInt32(TestContext.DataRow["FirstNumber"]);

Exécuter le test et afficher les résultats

Une fois que vous avez fini d’écrire une méthode de test, générez le projet de test. La méthode de test apparaît dans l’Explorateur de tests, dans le groupe Tests non exécutés. Tandis que vous exécutez, écrivez et réexécutez vos tests, l’Explorateur de tests affiche les résultats dans les groupes Échecs de tests, Tests réussis et Tests non exécutés. Vous pouvez choisir Exécuter tout pour exécuter tous vos tests ou Exécuter pour sélectionner un sous-ensemble de tests à exécuter.

La barre des résultats des tests en haut de l’Explorateur de tests s’anime pendant l’exécution de votre test. À la fin de la série de tests, la barre est verte en cas de réussite de tous les tests, ou rouge en cas d’échec de l’un des tests. Un résumé de la série de tests s’affiche dans le volet d’informations, en bas de la fenêtre Explorateur de tests. Sélectionnez un test pour en afficher les détails dans le volet inférieur.

Notes

Il existe un résultat pour chaque ligne de données et un résultat récapitulatif. Si le test a réussi sur chaque ligne de données, le récapitulatif de l’exécution indique Réussite. Si le test a échoué sur une ou plusieurs lignes de données, le récapitulatif de l’exécution indique Échec.

Si vous avez exécuté la méthode AddIntegers_FromDataRowTest, AddIntegers_FromDynamicDataTest ou AddIntegers_FromDataSourceTest dans notre exemple, la barre des résultats devient rouge et la méthode de test est déplacée vers le groupe Échecs de tests. Un test piloté par les données échoue si l’une des méthodes itérées de la source de données échoue également. Quand vous choisissez un test piloté par les données à l’état d’échec dans la fenêtre Explorateur de tests, le volet d’informations affiche les résultats de chaque itération identifiée par l’index de ligne de données. Dans notre exemple, il semble que l’algorithme AddIntegers ne gère pas correctement les valeurs négatives.

Quand la méthode testée est corrigée et que le test est réexécuté, la barre des résultats devient verte et la méthode de test est déplacée vers le groupe Tests réussis.