Procédure pas à pas : créer et exécuter des tests unitaires pour le code managéWalkthrough: Create and Run Unit Tests for Managed Code

Cet article décrit la création, l’exécution et la personnalisation d’une série de tests unitaires à l’aide du framework de tests unitaires Microsoft pour le code managé et de l’explorateur de tests de Visual Studio.This article steps you through creating, running, and customizing a series of unit tests using the Microsoft unit test framework for managed code and Visual Studio Test Explorer. Vous commencez avec un projet C# qui est en développement, vous créez des tests qui utilisent son code, vous exécutez les tests et vous examinez les résultats.You start with a C# project that is under development, create tests that exercise its code, run the tests, and examine the results. Ensuite, vous pouvez modifier le code de votre projet et réexécuter les tests.Then you can change your project code and rerun the tests.

Note

Cette procédure pas à pas utilise le framework de tests unitaires Microsoft pour le code managé.This walkthrough uses the Microsoft unit test framework for managed code. L’explorateur de tests peut également exécuter des tests depuis des frameworks de tests unitaires tiers qui ont des adaptateurs pour l’explorateur de tests.Test Explorer also can run tests from third party unit test frameworks that have adapters for Test Explorer. Pour plus d’informations, consultez Installer des frameworks de tests unitaires tiersFor more information, see Install third-party unit test frameworks

Note

Pour plus d’informations sur la façon d’exécuter des tests à partir d’une ligne de commande, consultez Procédure pas à pas : utilisation de l’utilitaire de test de ligne de commande.For information about how to run tests from a command line, see Walkthrough: Use the command-line test utility.

PrérequisPrerequisites

Créer un projet de testCreate a project to test

  1. Ouvrez Visual Studio.Open Visual Studio.

  2. Dans le menu Fichier, sélectionnez Nouveau > Projet.On the File menu, select New > Project.

    La boîte de dialogue Nouveau projet s’affiche.The New Project dialog box appears.

  3. Sous Modèles installés, cliquez sur Visual C#.Under Installed Templates, click Visual C#.

  4. Dans la liste de types d’applications, cliquez sur Bibliothèque de classes.In the list of application types, click Class Library.

  5. Dans la zone Nom, tapez Bank, puis cliquez sur OK.In the Name box, type Bank and then click OK.

    Le nouveau projet Bank est créé et affiché dans l’Explorateur de solutions, avec le fichier Class1.cs ouvert dans l’éditeur de code.The new Bank project is created and displayed in Solution Explorer with the Class1.cs file open in the code editor.

    Note

    Si Class1.cs n’est pas ouvert dans l’éditeur de code, double-cliquez sur le fichier Class1.cs dans l’Explorateur de solutions pour l’ouvrir.If Class1.cs is not open in the Code Editor, double-click the file Class1.cs in Solution Explorer to open it.

  6. Copiez le code source à partir de l’Exemple de projet pour la création de tests unitaires, puis remplacez le contenu d’origine de Class1.cs par le code copié.Copy the source code from the Sample project for creating unit tests, and replace the original contents of Class1.cs with the copied code.

  7. Enregistrez le fichier avec le nom BankAccount.cs.Save the file as BankAccount.cs.

  8. Dans le menu Générer , cliquez sur Générer la solution.On the Build menu, click Build Solution.

Vous avez maintenant un projet nommé Bank.You now have a project named Bank. Il contient le code source à tester et des outils avec lesquels le tester.It contains source code to test and tools to test it with. L’espace de noms pour Bank, BankAccountNS, contient la classe publique BankAccount, dont vous testerez les méthodes dans les procédures suivantes.The namespace for Bank, BankAccountNS, contains the public class BankAccount, whose methods you'll test in the following procedures.

Dans cet article, les tests se concentrent sur la méthode Debit.In this article, the tests focus on the Debit method. Cette méthode est appelée quand de l’argent est retiré d’un compte.The Debit method is called when money is withdrawn from an account. Voici la définition de la méthode :Here is the method definition:

// Method to be tested.
public void Debit(double amount)
{
    if(amount > m_balance)
    {
        throw new ArgumentOutOfRangeException("amount");
    }
    if (amount < 0)
    {
        throw new ArgumentOutOfRangeException("amount");
    }
    m_balance += amount;
}

Créer un projet de test unitaireCreate a unit test project

  1. Dans le menu Fichier, sélectionnez Ajouter > Nouveau projet.On the File menu, select Add > New Project.

  2. Dans la boîte de dialogue Nouveau Projet, développez Installé, développez Visual C#, puis choisissez Test.In the New Project dialog box, expand Installed, expand Visual C#, and then choose Test.

  3. Dans la liste des modèles, sélectionnez Projet de test unitaire.From the list of templates, select Unit Test Project.

  4. Dans la zone Nom, entrez BankTests, puis sélectionnez OK.In the Name box, enter BankTests, and then select OK.

    Le projet BankTests est ajouté à la solution Bank.The BankTests project is added to the Bank solution.

  5. Dans le projet BankTests, ajoutez une référence au projet Bank.In the BankTests project, add a reference to the Bank project.

    Dans l’Explorateur de solutions, sélectionnez Références dans le projet BankTests puis choisissez Ajouter une référence dans le menu contextuel.In Solution Explorer, select References in the BankTests project and then choose Add Reference from the context menu.

  6. Dans la boîte de dialogue Gestionnaire de références, développez Solution puis cochez l’élément Bank .In the Reference Manager dialog box, expand Solution and then check the Bank item.

Créer la classe de testCreate the test class

Créez une classe de test pour vérifier la classe BankAccount.Create a test class to verify the BankAccount class. Vous pouvez utiliser le fichier UnitTest1.cs qui a été généré par le modèle de projet, mais donnez au fichier et à la classe des noms plus descriptifs.You can use the UnitTest1.cs file that was generated by the project template, but give the file and class more descriptive names. Vous pouvez effectuer cela en une seule étape en renommant le fichier dans l’Explorateur de solutions.You can do that in one step by renaming the file in Solution Explorer.

Renommer un fichier de classeRename a class file

Dans l’Explorateur de solutions, sélectionnez le fichier UnitTest1.cs dans le projet BankTests.In Solution Explorer, select the UnitTest1.cs file in the BankTests project. Dans le menu contextuel, choisissez Renommer, puis renommez le fichier BankAccountTests.cs.From the context menu, choose Rename, and then rename the file to BankAccountTests.cs. Choisissez Oui dans la boîte de dialogue qui vous demande si vous souhaitez renommer toutes les références à l’élément de code UnitTest1 dans le projet.Choose Yes on the dialog that asks if you want to rename all references to the code element UnitTest1 in the project.

Cette opération a pour effet de remplacer le nom de la classe par BankAccountTests.This step changes the name of the class to BankAccountTests. Le fichier BankAccountTests.cs contient maintenant le code suivant :The BankAccountTests.cs file now contains the following code:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

Ajouter une instruction using au projet testéAdd a using statement to the project under test

Vous pouvez également ajouter une instruction using à la classe pour pouvoir appeler le projet testé sans utiliser de noms complets.You can also add a using statement to the class to be able to call into the project under test without using fully qualified names. En haut du fichier de classe, ajoutez :At the top of the class file, add:

using BankAccountNS;

Spécifications de la classe de testTest class requirements

Voici la configuration minimale requise pour une classe de test :The minimum requirements for a test class are:

  • L’attribut [TestClass] est requis dans le framework de tests unitaires Microsoft pour le code managé pour toute classe qui contient les méthodes de test unitaire à exécuter dans l’explorateur de tests.The [TestClass] attribute is required in the Microsoft unit testing framework for managed code for any class that contains unit test methods that you want to run in Test Explorer.

  • Chaque méthode de test à exécuter avec l’explorateur de tests doit avoir l’attribut [TestMethod].Each test method that you want Test Explorer to run must have the [TestMethod] attribute.

Vous pouvez avoir d’autres classes dans un projet de test unitaire qui n’ont pas l’attribut [TestClass] , et vous pouvez avoir d’autres méthodes dans les classes de test qui n’ont pas l’attribut [TestMethod] .You can have other classes in a unit test project that do not have the [TestClass] attribute, and you can have other methods in test classes that do not have the [TestMethod] attribute. Vous pouvez utiliser ces autres classes et méthodes dans vos méthodes de test.You can use these other classes and methods in your test methods.

Créer la première méthode de testCreate the first test method

Dans cette procédure, vous écrivez des méthodes de test unitaire pour vérifier le comportement de la méthode Debit de la classe BankAccount.In this procedure, you'll write unit test methods to verify the behavior of the Debit method of the BankAccount class. La méthode Debit est présentée plus haut dans cet article.The Debit method is shown previously in this article.

Il existe au moins trois comportements à vérifier :There are at least three behaviors that need to be checked:

  • La méthode lève une exception ArgumentOutOfRangeException si le montant du débit est supérieur au solde.The method throws an ArgumentOutOfRangeException if the debit amount is greater than the balance.

  • La méthode lève l’exception ArgumentOutOfRangeException si le montant du débit est inférieur à zéro.The method throws ArgumentOutOfRangeException if the debit amount is less than zero.

  • Si le montant du débit est valide, la méthode le soustrait du solde du compte.If the debit amount is valid, the method subtracts the debit amount from the account balance.

Conseil

Vous pouvez supprimer la méthode TestMethod1 par défaut, car vous ne l’utiliserez pas dans cette procédure pas à pas.You can delete the default TestMethod1 method, because you won't use it in this walkthrough.

Pour créer une méthode de testTo create a test method

Le premier test vérifie qu’un montant valide (c’est-à-dire, inférieur au solde du compte et supérieur à zéro) retire le montant approprié du compte.The first test verifies that a valid amount (that is, one that is less than the account balance and greater than zero) withdraws the correct amount from the account. Ajoutez la méthode suivante à cette classe BankAccountTests :Add the following method to that BankAccountTests class:

[TestMethod]
public void Debit_WithValidAmount_UpdatesBalance()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 4.55;
    double expected = 7.44;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    account.Debit(debitAmount);

    // Assert
    double actual = account.Balance;
    Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly");
}

La méthode est simple : elle met en place un nouvel objet BankAccount avec un solde de début, puis elle retire un montant valide.The method is straightforward: it sets up a new BankAccount object with a beginning balance, and then withdraws a valid amount. Elle utilise la méthode AreEqual pour vérifier que le solde de fin est conforme à ce qui est attendu.It uses the AreEqual method to verify that the ending balance is as expected.

Spécifications des méthodes de testTest method requirements

Une méthode de test doit répondre aux spécifications suivantes :A test method must meet the following requirements:

  • Elle est décorée avec l’attribut [TestMethod].It's decorated with the [TestMethod] attribute.

  • Il retourne void.It returns void.

  • Elle ne peut pas avoir de paramètres.It cannot have parameters.

Générer et exécuter le testBuild and run the test

  1. Dans le menu Générer , choisissez Générer la solution.On the Build menu, choose Build Solution.

    En l’absence d’erreurs, l’explorateur de tests apparaît avec Debit_WithValidAmount_UpdatesBalance répertorié dans le groupe Tests non exécutés.If there are no errors, Test Explorer appears with Debit_WithValidAmount_UpdatesBalance listed in the Not Run Tests group.

    Conseil

    Si l’explorateur de tests n’apparaît pas après une génération réussie, sélectionnez Test dans le menu, puis Fenêtres, puis Explorateur de tests.If Test Explorer does not appear after a successful build, choose Test on the menu, then choose Windows, and then choose Test Explorer.

  2. Sélectionnez Exécuter tout pour exécuter le test.Choose Run All to run the test. Pendant que le test s’exécute, la barre d’état en haut de la fenêtre s’anime.While the test is running, the status bar at the top of the window is animated. À l’issue de la série de tests, la barre devient verte si toutes les méthodes de test ont réussi, ou rouge si l’un des tests a échoué.At the end of the test run, the bar turns green if all the test methods pass, or red if any of the tests fail.

  3. Dans ce cas, le test échoue.In this case, the test fails. La méthode de test est déplacée vers le groupe Échecs de tests.The test method is moved to the Failed Tests group. Sélectionnez la méthode dans l’explorateur de tests pour en afficher les détails en bas de la fenêtre.Select the method in Test Explorer to view the details at the bottom of the window.

Vérifier votre code et exécuter à nouveau vos testsFix your code and rerun your tests

Analyser les résultats des testsAnalyze the test results

Le résultat de test contient un message qui décrit l’échec.The test result contains a message that describes the failure. Pour la méthode AreEquals, le message affiche ce qui était attendu (le paramètre Expected<valeur>) et ce qui a été reçu réellement (le paramètre Actual<valeur>).For the AreEquals method, the message displays what was expected (the Expected<value> parameter) and what was actually received (the Actual<value> parameter). Alors que le solde aurait dû diminuer, il a augmenté du montant du retrait.You expected the balance to decrease, but instead it actually increased by the amount of the withdrawal.

Le test unitaire a découvert un bogue : le montant du retrait est ajouté au solde du compte quand il doit être soustrait.The unit test has uncovered a bug: the amount of the withdrawal is added to the account balance when it should be subtracted.

Corriger le bogueCorrect the bug

Pour corriger l’erreur, remplacez la ligne :To correct the error, replace the line:

m_balance += amount;

Par :with:

m_balance -= amount;

Réexécuter le testRerun the test

Dans l’explorateur de tests, choisissez Exécuter tout pour réexécuter le test.In Test Explorer, choose Run All to rerun the test. La barre rouge/verte devient verte pour indiquer que le test a réussi, puis le test est déplacé vers le groupe Tests réussis.The red/green bar turns green to indicate that the test passed, and the test is moved to the Passed Tests group.

Utiliser les tests unitaires pour améliorer votre codeUse unit tests to improve your code

Cette section décrit comment un processus itératif d’analyse, de développement de test unitaire et de refactorisation peut vous aider à rendre votre code de production plus fiable et efficace.This section describes how an iterative process of analysis, unit test development, and refactoring can help you make your production code more robust and effective.

Analyser les problèmesAnalyze the issues

Vous avez créé une méthode de test pour confirmer qu’un montant valide est correctement déduit dans la méthode Debit.You've created a test method to confirm that a valid amount is correctly deducted in the Debit method. À présent, vérifiez que la méthode lève une exception ArgumentOutOfRangeException si le montant du débit est :Now, verify that the method throws an ArgumentOutOfRangeException if the debit amount is either:

  • supérieur au solde ougreater than the balance, or
  • inférieur à zéro.less than zero.

Créer les méthodes de testCreate the test methods

Créez une méthode de test pour vérifier que le comportement est correct quand le montant du débit est inférieur à zéro :Create a test method to verify correct behavior when the debit amount is less than zero:

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = -100.00;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    account.Debit(debitAmount);

    // Assert is handled by the ExpectedException attribute on the test method.
}

Utilisez l’attribut ExpectedExceptionAttribute pour déclarer que l’exception adéquate a été levée.Use the ExpectedExceptionAttribute attribute to assert that the correct exception has been thrown. L’attribut entraîne l’échec du test à moins qu’une exception ArgumentOutOfRangeException ne soit levée.The attribute causes the test to fail unless an ArgumentOutOfRangeException is thrown. Si vous modifiez temporairement la méthode testée pour lever une ApplicationException plus générique quand le montant du débit est inférieur à zéro, le test se comporte correctement—en l’occurrence, il échoue.If you temporarily modify the method under test to throw a more generic ApplicationException when the debit amount is less than zero, the test behaves correctly—that is, it fails.

Pour tester le cas où le montant retiré est supérieur au solde, effectuez les étapes suivantes :To test the case when the amount withdrawn is greater than the balance, do the following steps:

  1. Créez une méthode de test nommée Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange.Create a new test method named Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange.

  2. Copiez le corps de la méthode depuis Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange vers la nouvelle méthode.Copy the method body from Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange to the new method.

  3. Définissez debitAmount sur un nombre supérieur au solde.Set the debitAmount to a number greater than the balance.

Exécuter les testsRun the tests

L’exécution des deux méthodes de test montre que les tests fonctionnent correctement.Running the two test methods demonstrates that the tests work correctly.

Poursuivre l’analyseContinue the analysis

Toutefois, les deux dernières méthodes de test sont également troublantes.However, the last two test methods are also troubling. Vous ne pouvez pas savoir avec certitude quelle est la condition, dans la méthode testée, qui lève l’exception quand un des tests est exécuté.You can't be certain which condition in the method under test throws the exception when either test is run. Si vous pouviez distinguer les deux conditions, c’est-à-dire, un montant de débit négatif ou un montant supérieur au solde, vous auriez davantage confiance dans les tests.Some way of differentiating the two conditions, that is a negative debit amount or an amount greater than the balance, would increase your confidence in the tests.

Examinez encore la méthode testée ; vous pouvez constater que les deux instructions conditionnelles utilisent un constructeur ArgumentOutOfRangeException qui accepte uniquement le nom de l’argument comme paramètre :Look at the method under test again, and notice that both conditional statements use an ArgumentOutOfRangeException constructor that just takes name of the argument as a parameter:

throw new ArgumentOutOfRangeException("amount");

Vous pouvez utiliser un constructeur qui fournit des informations beaucoup plus riches : ArgumentOutOfRangeException(String, Object, String) inclut le nom de l’argument, la valeur de l’argument et un message défini par l’utilisateur.There is a constructor you can use that reports far richer information: ArgumentOutOfRangeException(String, Object, String) includes the name of the argument, the argument value, and a user-defined message. Vous pouvez refactoriser la méthode testée pour utiliser ce constructeur.You can refactor the method under test to use this constructor. Encore mieux, vous pouvez utiliser les membres de type disponibles publiquement pour spécifier les erreurs.Even better, you can use publicly available type members to specify the errors.

Refactoriser le code testéRefactor the code under test

Tout d’abord, définissez deux constantes pour les messages d’erreur au niveau de la portée d’une classe.First, define two constants for the error messages at class scope. Placez-les dans la classe testée (BankAccount) :Put these in the class under test, BankAccount:

public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount is less than zero";

Ensuite, modifiez les deux instructions conditionnelles dans la méthode Debit :Then, modify the two conditional statements in the Debit method:

    if (amount > m_balance)
    {
        throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
    }

    if (amount < 0)
    {
        throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
    }

Refactoriser les méthodes de testRefactor the test methods

Supprimez l’attribut de méthode de test ExpectedException et, à la place, interceptez l’exception levée et vérifiez le message associé.Remove the ExpectedException test method attribute and instead, catch the thrown exception and verify its associated message. La méthode StringAssert.Contains permet de comparer deux chaînes.The StringAssert.Contains method provides the ability to compare two strings.

À présent, Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange peut se présenter comme suit :Now, the Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange might look like this:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
    }
}

Retester, réécrire et réanalyserRetest, rewrite, and reanalyze

Supposez qu’il y a un bogue dans la méthode testée et que la méthode Debit ne lève même pas d’exception ArgumentOutOfRangeException, et ne génère pas non plus le bon message avec l’exception.Assume there's a bug in the method under test, and the Debit method doesn't even throw an ArgumentOutOfRangeException, nevermind output the correct message with the exception. Actuellement, la méthode de test ne gère pas ce cas.Currently, the test method doesn't handle this case. Si la valeur de debitAmount est valide (autrement dit, inférieure au solde mais supérieure à zéro), aucune exception n’est interceptée et l’assertion ne se déclenche donc jamais.If the debitAmount value is valid (that is, less than the balance but greater than zero), no exception is caught, so the assert never fires. Pourtant, la méthode de test réussit.Yet, the test method passes. Cela ne convient pas, car vous souhaitez que la méthode de test échoue si aucune exception n’est levée.This is not good, because you want the test method to fail if no exception is thrown.

Il s’agit d’un bogue dans la méthode de test.This is a bug in the test method. Pour résoudre le problème, ajoutez une assertion Fail à la fin de la méthode de test pour gérer le cas où aucune exception n’est levée.To resolve the issue, add an Fail assert at the end of the test method to handle the case where no exception is thrown.

Cependant, une réexécution du test montre que le test échoue maintenant si l’exception correcte est interceptée.But rerunning the test shows that the test now fails if the correct exception is caught. Le bloc catch intercepte l’exception, mais la méthode continue à s’exécuter et échoue au niveau de la nouvelle assertion Fail.The catch block catches the exception, but the method continues to execute and it fails at the new Fail assert. Pour résoudre ce problème, ajoutez une instruction return après StringAssert dans le bloc catch.To resolve this problem, add a return statement after the StringAssert in the catch block. La réexécution du test confirme que vous avez résolu ce problème.Rerunning the test confirms that you've fixed this problem. La version finale de Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange ressemble à ceci :The final version of the Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange looks like this:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
        return;
    }

    Assert.Fail("The expected exception was not thrown.");
}

Les améliorations apportées au code du test ont abouti à des méthodes de test plus robustes et plus informatives.The improvements to the test code led to more robust and informative test methods. Mais plus important encore, elles ont également amélioré le code testé.But more importantly, they also improved the code under test.