Guide pratique pour écrire des tests pour des DLL C++How to: Write Unit tests for C++ DLLs

Cette procédure pas à pas décrit comment développer une DLL C++ native en utilisant la méthodologie des tests en premier.This walkthrough describes how to develop a native C++ DLL using test-first methodology. Les étapes de base sont les suivantes :The basic steps are as follows:

  1. Créer un projet de test natif.Create a Native Test Project. Le projet de test se trouve dans la même solution que le projet DLL.The test project is located in the same solution as the DLL project.

  2. Créer un projet DLL.Create a DLL Project. Cette procédure pas à pas crée une DLL, mais la procédure de test d'une DLL existante est similaire.This walkthrough creates a new DLL, but the procedure for testing an existing DLL is similar.

  3. Rendre les fonctions DLL visibles par les tests.Make the DLL functions visible to the tests.

  4. Augmenter itérativement les tests.Iteratively augment the tests. Nous recommandons un cycle « Rouge-Vert-Refactoriser », dans lequel le développement du code est conduit par les tests.We recommend a "red-green-refactor" cycle, in which development of the code is led by the tests.

  5. Déboguer les tests ayant échoué.Debug failing tests. Vous pouvez exécuter les tests en mode débogage.You can run tests in debug mode.

  6. Refactoriser tout en maintenant les tests inchangés.Refactor while keeping the tests unchanged. La refactorisation signifie une amélioration de la structure du code sans modification de son comportement externe.Refactoring means improving the structure of the code without changing its external behavior. Vous pouvez procéder ainsi pour améliorer les performances, l'extensibilité ou la lisibilité du code.You can do it to improve the performance, extensibility, or readability of the code. Comme le but n'est pas de changer le comportement, vous ne modifiez pas les tests en même temps que vous effectuez une modification de refactorisation du code.Because the intention is not to change the behavior, you do not change the tests while making a refactoring change to the code. Les tests permettent de vérifier que vous n’introduisez pas de bogues lors de la refactorisation.The tests help make sure that you do not introduce bugs while you are refactoring.

  7. Vérifier la couverture.Check coverage. Les tests unitaires sont plus utiles lorsqu'ils impliquent une plus grande partie de votre code.Unit tests are more useful when they exercise more of your code. Vous pouvez découvrir quelles parties de votre code ont été utilisées par les tests.You can discover which parts of your code have been used by the tests.

  8. Isoler les unités des ressources externes.Isolate units from external resources. En règle générale, une DLL dépend des autres composants du système que vous développez, tels que les autres DLL, les bases de données ou les sous-systèmes à distance.Typically, a DLL is dependent on other components of the system that you are developing, such as other DLLs, databases, or remote subsystems. Il est utile de tester chaque unité isolément de ses dépendances.It is useful to test each unit in isolation from its dependencies. Les composants externes peuvent ralentir les tests.External components can make tests run slowly. Pendant le développement, les autres composants peuvent ne pas être achevés.During development, the other components might not be complete.

Créer un projet de test unitaire natifCreate a native unit test project

  1. Dans le menu Fichier, choisissez Nouveau | Projet.On the File menu, choose New | Project.

    Dans la boîte de dialogue, développez Installé | Modèles| Visual C++ | Test.In the dialog box, expand Installed | Templates | Visual C++ | Test.

    Choisissez le modèle Projet de test unitaire natif ou un autre framework installé de votre choix.Choose the Native Unit Test Project template, or whatever installed framework you prefer. Si vous choisissez un autre modèle, comme Google Test ou Boost.Test, les principes de base sont les mêmes, bien que certains détails diffèrent.If you choose another template such as Google Test or Boost.Test, the basic principles are the same although some details will differ.

    Dans cette procédure pas à pas, le projet de test se nomme NativeRooterTest.In this walkthrough, the test project is named NativeRooterTest.

    Création d’un projet de test unitaire C++Creating a C++ Unit Test Project

  2. Dans le nouveau projet, inspectez unittest1.cppIn the new project, inspect unittest1.cpp

    Projet de test avec TEST_CLASS et TEST_METHODTest project with TEST_CLASS and TEST_METHOD

    Notez que :Notice that:

    • Chaque test est défini à l'aide de TEST_METHOD(YourTestName){...}.Each test is defined by using TEST_METHOD(YourTestName){...}.

      Vous n'avez pas à écrire une signature de fonction classique.You do not have to write a conventional function signature. La signature est créée par la macro TEST_METHOD.The signature is created by the macro TEST_METHOD. La macro génère une fonction d'instance qui retourne void.The macro generates an instance function that returns void. Elle génère également une fonction statique qui retourne des informations sur la méthode de test.It also generates a static function that returns information about the test method. Ces informations permettent à l'Explorateur de tests de trouver la méthode.This information allows the test explorer to find the method.

    • Les méthodes de test sont regroupées en classes à l'aide de TEST_CLASS(YourClassName){...}.Test methods are grouped into classes by using TEST_CLASS(YourClassName){...}.

      Lorsque les tests sont exécutés, une instance de chaque classe de test est créée.When the tests are run, an instance of each test class is created. Les méthodes de test sont appelées dans un ordre non défini.The test methods are called in an unspecified order. Vous pouvez définir des méthodes spéciales qui sont appelées avant et après chaque module, classe ou méthode.You can define special methods that are invoked before and after each module, class, or method.

  3. Vérifiez que les tests s'exécutent dans l'Explorateur de tests :Verify that the tests run in Test Explorer:

    1. Insérez le code de test :Insert some test code:

      TEST_METHOD(TestMethod1)
      {
          Assert::AreEqual(1,1);
      }
      

      Notez que la classe Assert fournit plusieurs méthodes statiques que vous pouvez utiliser pour vérifier les résultats dans les méthodes de test.Notice that the Assert class provides several static methods that you can use to verify results in test methods.

    2. Dans le menu Test, choisissez Exécuter | Tous les tests.On the Test menu, choose Run | All Tests.

      Le test est généré et s'exécute.The test builds and runs.

      L'Explorateur de tests s'affiche.Test Explorer appears.

      Le test s'affiche sous Tests réussis.The test appears under Passed Tests.

      Explorateur de tests unitaires avec un test réussiUnit Test Explorer with one passed test

Créer un projet DLLCreate a DLL project

  1. Créez un projet Visual C++ à l'aide du modèle Projet Win32 .Create a Visual C++ project by using the Win32 Project template.

    Dans cette procédure pas à pas, le projet se nomme RootFinder.In this walkthrough, the project is named RootFinder.

    Création d’un projet Win32 C++Creating a C++ Win32 project

  2. Sélectionnez DLL et Exporter les symboles dans l'Assistant Application Win32.Select DLL and Export Symbols in the Win32 Application Wizard.

    L'option Exporter les symboles génère une macro pratique qui permet de déclarer les méthodes exportées.The Export Symbols option generates a convenient macro that you can use to declare exported methods.

    Assistant Projet C++ défini pour les symboles DLL et d’exportationC++ project wizard set for DLL and Export Symbols

  3. Déclarez une fonction exportée dans le fichier .h principal :Declare an exported function in the principal .h file:

    Nouveau projet de code de la DLL et fichier .h avec macros APINew DLL code project and .h file with API macros

    Le déclarateur __declspec(dllexport) permet que les membres publics et protégés de la classe soient visibles en dehors de la DLL.The declarator __declspec(dllexport) causes the public and protected members of the class to be visible outside the DLL. Pour plus d'informations, consultez Utilisation de dllimport et dllexport dans les classes C++.For more information, see Using dllimport and dllexport in C++ Classes.

  4. Dans le fichier .cpp principal, ajoutez un corps minimal pour la fonction :In the principal .cpp file, add a minimal body for the function:

        // Find the square root of a number.
        double CRootFinder::SquareRoot(double v)
        {
            return 0.0;
        }
    

Associer le projet de test au projet DLLCouple the test project to the DLL project

  1. Ajoutez le projet DLL aux références de projet du projet de test :Add the DLL project to the project references of the test project:

    1. Ouvrez les propriétés du projet de test et choisissez Propriétés communes, Structure et références.Open the properties of the test project and choose Common Properties, Framework and References.

      Propriétés du projet C++ - Framework et référencesC++ project properties | Framework and References

    2. Choisissez Ajouter une nouvelle référence.Choose Add New Reference.

      Dans la boîte de dialogue Ajouter une référence , sélectionnez le projet DLL et choisissez Ajouter.In the Add Reference dialog box, select the DLL project and choose Add.

      Propriétés du projet C++ - Ajouter une nouvelle référenceC++ project properties | Add New Reference

  2. Dans le fichier .cpp de test unitaire principal, incluez le fichier .h du code de la DLL :In the principal unit test .cpp file, include the .h file of the DLL code:

    #include "..\RootFinder\RootFinder.h"
    
  3. Ajoutez un test de base qui utilise la fonction exportée :Add a basic test that uses the exported function:

    TEST_METHOD(BasicTest)
    {
       CRootFinder rooter;
       Assert::AreEqual(
          // Expected value:
          0.0,
          // Actual value:
          rooter.SquareRoot(0.0),
          // Tolerance:
          0.01,
         // Message:
         L"Basic test failed",
         // Line number - used if there is no PDB file:
         LINE_INFO());
    }
    
  4. Générez la solution.Build the solution.

    Le nouveau test s'affiche dans l'Explorateur de tests.The new test appears in Test Explorer.

  5. Dans l'Explorateur de tests, choisissez Exécuter tout.In Test Explorer, choose Run All.

    Explorateur de tests unitaires - Réussite du test de baseUnit Test Explorer - Basic Test passed

    Vous avez configuré le test et les projets de code, et vérifié que vous pouviez exécuter des tests exécutant les fonctions du projet de code.You have set up the test and the code projects, and verified that you can run tests that run functions in the code project. Maintenant, vous pouvez commencer à écrire le code et les tests réels.Now you can begin to write real tests and code.

Augmenter itérativement les tests et les faire réussirIteratively augment the tests and make them pass

  1. Ajoutez un nouveau test :Add a new test:

    TEST_METHOD(RangeTest)
    {
      CRootFinder rooter;
      for (double v = 1e-6; v < 1e6; v = v * 3.2)
      {
        double actual = rooter.SquareRoot(v*v);
        Assert::AreEqual(v, actual, v/1000);
      }
    }
    

    Conseil

    Nous vous recommandons de ne pas modifier les tests ayant réussi.We recommend that you do not change tests that have passed. Ajoutez à la place un nouveau test, mettez à jour le code afin que le test réussisse, puis ajoutez un autre test, et ainsi de suite.Instead, add a new test, update the code so that the test passes, and then add another test, and so on.

    Lorsque vos utilisateurs modifient leurs spécifications, désactivez les tests qui ne sont plus corrects.When your users change their requirements, disable the tests that are no longer correct. Écrivez de nouveaux tests et utilisez-les l'un après l'autre, de la même façon incrémentielle.Write new tests and make them work one at a time, in the same incremental manner.

  2. Générez la solution, puis, dans l'Explorateur de tests, choisissez Exécuter tout.Build the solution, and then in Test Explorer, choose Run All.

    Le nouveau test échoue.The new test fails.

    Échec de RangeTestThe RangeTest fails

    Conseil

    Vérifiez que chaque test échoue immédiatement après que vous l'avez écrit.Verify that each test fails immediately after you have written it. Vous évitez ainsi de commettre l'erreur d'écrire un test qui n'échoue jamais.This helps you avoid the easy mistake of writing a test that never fails.

  3. Améliorez le code de votre DLL afin que le nouveau test réussisse :Enhance your DLL code so that the new test passes:

    #include <math.h>
    ...
    double CRootFinder::SquareRoot(double v)
    {
      double result = v;
      double diff = v;
      while (diff > result/1000)
      {
        double oldResult = result;
        result = result - (result*result - v)/(2*result);
        diff = abs (oldResult - result);
      }
      return result;
    }
    
  4. Générez la solution, puis, dans l'Explorateur de tests, choisissez Exécuter tout.Build the solution and then in Test Explorer, choose Run All.

    Les deux tests réussissent.Both tests pass.

    Explorateur de tests unitaires - Réussite du test de plageUnit Test Explorer - Range Test passed

    Conseil

    Développez le code en ajoutant les tests individuellement.Develop code by adding tests one at a time. Assurez-vous que tous les tests réussissent après chaque itération.Make sure that all the tests pass after each iteration.

Déboguer un test ayant échouéDebug a failing test

  1. Ajoutez un autre test :Add another test:

    #include <stdexcept>
    ...
    // Verify that negative inputs throw an exception.
    TEST_METHOD(NegativeRangeTest)
    {
      wchar_t message[200];
      CRootFinder rooter;
      for (double v = -0.1; v > -3.0; v = v - 0.5)
      {
        try
        {
          // Should raise an exception:
          double result = rooter.SquareRoot(v);
    
          _swprintf(message, L"No exception for input %g", v);
          Assert::Fail(message, LINE_INFO());
        }
        catch (std::out_of_range ex)
        {
          continue; // Correct exception.
        }
        catch (...)
        {
          _swprintf(message, L"Incorrect exception for %g", v);
          Assert::Fail(message, LINE_INFO());
        }
      }
    }
    
  2. Générez la solution et choisissez Exécuter tout.Build the solution and choose Run All.

  3. Ouvrez le test ayant échoué (ou double-cliquez dessus).Open (or double-click) the failed test.

    L'échec d'assertion est mis en surbrillance.The failed assertion is highlighted. Le message d'échec est visible dans le volet de détails de l'Explorateur de tests.The failure message is visible in the detail pane of Test Explorer.

    Échec de NegativeRangeTestsNegativeRangeTests failed

  4. Pour voir pourquoi le test échoue, parcourez la fonction :To see why the test fails, step through the function:

    1. Définissez un point d'arrêt au début de la fonction SquareRoot.Set a breakpoint at the start of the SquareRoot function.

    2. Dans le menu contextuel du test ayant échoué, choisissez Déboguer les tests sélectionnés.On the shortcut menu of the failed test, choose Debug Selected Tests.

      Lorsque l'exécution s'arrête au point d'arrêt, parcourez le code.When the run stops at the breakpoint, step through the code.

  5. Insérez le code de la fonction que vous développez :Insert code in the function that you are developing:

    
    #include <stdexcept>
    ...
    double CRootFinder::SquareRoot(double v)
    {
        // Validate parameter:
        if (v < 0.0)
        {
          throw std::out_of_range("Can't do square roots of negatives");
        }
    
  6. Toutes les tests réussissent maintenant.All tests now pass.

    Tous les tests sont concluantsAll tests pass

Conseil

Si les tests individuels n’ont aucune dépendance qui les empêche d’être exécutés dans n’importe quel ordre, activez l’exécution parallèle des tests avec le bouton bascule UTE_parallelicon-small dans la barre d’outils.If individual tests have no dependencies that prevent them from being run in any order, turn on parallel test execution with the UTE_parallelicon-small toggle button on the toolbar. Cela peut réduire sensiblement le temps nécessaire pour exécuter tous les tests.This can noticeably reduce the time taken to run all the tests.

Refactoriser le code sans modifier les testsRefactor the code without changing tests

  1. Simplifiez le calcul central de la fonction SquareRoot :Simplify the central calculation in the SquareRoot function:

    // old code:
    //   result = result - (result*result - v)/(2*result);
    // new code:
         result = (result + v/result)/2.0;
    
  2. Générez la solution et choisissez Exécuter tout, pour vous assurer que vous n'avez pas introduit d'erreur.Build the solution and choose Run All, to make sure that you have not introduced an error.

    Conseil

    Un bon jeu de tests unitaires vous garantit que vous n'avez pas introduit de bogues lors de la modification du code.A good set of unit tests gives confidence that you have not introduced bugs when you change the code.

    Maintenez la refactorisation distincte des autres modifications.Keep refactoring separate from other changes.

Étapes suivantesNext steps

  • Isolement.Isolation. La plupart des DLL dépendent d'autres sous-systèmes tels que des bases de données et d'autres DLL.Most DLLs are dependent on other subsystems such as databases and other DLLs. Ces autres composants sont souvent développés en parallèle.These other components are often developed in parallel. Pour permettre que le test unitaire soit exécuté pendant que les autres composants ne sont pas encore disponibles, vous devez remplacerTo allow unit testing to be performed while the other components are not yet available, you have to substitute mock or

  • Tests de vérification de build.Build Verification Tests. Des tests peuvent être effectués sur le serveur de builds de votre équipe à des intervalles définis.You can have tests performed on your team's build server at set intervals. Cela garantit que les bogues ne sont pas introduits lors de l'intégration du travail de plusieurs membres de l'équipe.This ensures that bugs are not introduced when the work of several team members is integrated.

  • Tests d’archivage.Checkin tests. Vous pouvez imposer que certains tests soient effectués avant que chaque membre de l'équipe n'archive le code dans le contrôle de code source.You can mandate that some tests are performed before each team member checks code into source control. Il s'agit généralement d'un sous-ensemble de l'ensemble complet des tests de vérification de build.Typically this is a subset of the complete set of build verification tests.

    Vous pouvez également imposer un niveau minimal de couverture du code.You can also mandate a minimum level of code coverage.

Voir aussiSee also