Guide pratique pour tester une DLL Visual C++How to test a Visual C++ DLL

Cette rubrique décrit une manière de créer des tests unitaires pour une DLL C++ destinée à des applications de plateforme Windows universelle (UWP) avec le framework de test Microsoft pour C++.This topic describes one way to create unit tests for a C++ DLL for Universal Windows Platform (UWP) apps with the Microsoft Test Framework for C++. La DLL RooterLib illustre de vagues souvenirs de la théorie de limite du calcul en implémentant une fonction qui calcule une estimation de la racine carrée d'un nombre donné.The RooterLib DLL demonstrates vague memories of limit theory from the calculus by implementing a function that calculates an estimate of the square root of a given number. La DLL peut ensuite être incluse dans une application UWP pour montrer à l’utilisateur les choses amusantes qu’il est possible de faire avec les mathématiques.The DLL might then be included in a UWP app that shows a user the fun things that can be done with math.

Cette rubrique vous montre comment utiliser les tests unitaires comme première étape du développement.This topic shows you how to use unit testing as the first step in development. Dans cette approche, vous écrivez d'abord une méthode de test qui vérifie un comportement spécifique dans le système que vous testez, puis vous écrivez le code qui réussit le test.In this approach, you first write a test method that verifies a specific behavior in the system that you are testing and then you write the code that passes the test. En modifiant l'ordre des procédures suivantes, vous pouvez inverser cette stratégie de manière à écrire d'abord le code que vous souhaitez tester, puis à écrire les tests unitaires.By making changes in the order of the following procedures, you can reverse this strategy to first write the code that you want to test and then write the unit tests.

Cette rubrique crée également une solution Visual Studio unique et des projets distincts pour les tests unitaires et la DLL que vous souhaitez tester.This topic also creates a single Visual Studio solution and separate projects for the unit tests and the DLL that you want to test. Vous pouvez également inclure les tests unitaires directement dans le projet DLL, ou vous pouvez créer des solutions distinctes pour les tests unitaires et la DLL.You can also include the unit tests directly in the DLL project, or you can create separate solutions for the unit tests and the .DLL. Consultez la page Ajout de tests unitaires aux applications C++ existantes pour obtenir des conseils sur la structure à utiliser.See Adding unit tests to existing C++ applications for tips on which structure to use.

Créer la solution et le projet de test unitaireCreate the solution and the unit test project

  1. Dans le menu Fichier, choisissez Nouveau > Nouveau projet....On the File menu, choose New > New Project....

  2. Dans la boîte de dialogue Nouveau projet, développez Installé > Visual C++, puis choisissez Windows universel.In the New Project dialog, expand Installed > Visual C++ and choose Windows Universal. Choisissez ensuite Application de tests unitaires (Windows universel) dans la liste des modèles de projet.Then choose Unit Test App (Universal Windows) from the list of project templates.

  3. Nommez le projet RooterLibTests, spécifiez l’emplacement, nommez la solution RooterLib, puis vérifiez que la case Créer le répertoire pour la solution est cochée.Name the project RooterLibTests; specify the location; name the solution RooterLib; and make sure Create directory for solution is checked.

    Spécifier la solution, le nom du projet et l’emplacementSpecify the solution and project name and location

  4. Dans le nouveau projet, ouvrez unittest1.cpp.In the new project, open unittest1.cpp.

    unittest1.cppunittest1.cpp

    Prenez note de ce qui suit :Note 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. Pour plus d’informations, consultez Utilisation de Microsoft.VisualStudio.TestTools.CppUnitTestFramework dans MSDN Library.For more information, see Using Microsoft.VisualStudio.TestTools.CppUnitTestFramework in the MSDN Library.

Vérifier que les tests s’exécutent dans l’Explorateur de testsVerify 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, puis Exécuter tout.On the Test menu, choose Run and then choose Run All.

    Le projet de test est généré et exécuté.The test project builds and runs. La fenêtre de l’Explorateur de tests s’affiche, et le test est listé sous Tests réussis.The Test Explorer window appears, and the test is listed under Passed Tests. Le volet de résumé situé au bas de la fenêtre fournit des informations supplémentaires sur le test sélectionné.The Summary pane at the bottom of the window provides additional details about the selected test.

    Explorateur de testsTest Explorer

Ajouter le projet DLL à la solutionAdd the DLL project to the solution

  1. Dans l'Explorateur de solutions, choisissez le nom de la solution.In Solution Explorer, choose the solution name. Dans le menu contextuel, choisissez Ajouter, puis Ajouter un nouveau projet.From the shortcut menu, choose Add, and then Add New Project.

    Créer le projet RooterLibCreate the RooterLib project

  2. Dans la boîte de dialogue Ajouter un nouveau projet, choisissez DLL (applications UWP).In the Add New Project dialog box, choose DLL (UWP apps).

  3. Ajoutez le code suivant au fichier RooterLib.h :Add the following code to the RooterLib.h file:

    // The following ifdef block is the standard way of creating macros which make exporting
    // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS
    // symbol defined on the command line. This symbol should not be defined on any project
    // that uses this DLL. This way any other project whose source files include this file see
    // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols
    // defined with this macro as being exported.
    #ifdef ROOTERLIB_EXPORTS
    #define ROOTERLIB_API  __declspec(dllexport)
    #else
    #define ROOTERLIB_API __declspec(dllimport)
    #endif //ROOTERLIB_EXPORTS
    
    class ROOTERLIB_API CRooterLib {
    public:
        CRooterLib(void);
        double SquareRoot(double v);
    };
    

    Les commentaires expliquent la signification du bloc ifdef non seulement au développeur de la DLL, mais aussi à toute personne qui référence la DLL dans son projet.The comments explain the ifdef block not only to the developer of the dll, but to anyone who references the DLL in their project. Vous pouvez ajouter le symbole ROOTERLIB_EXPORTS à la ligne de commande en utilisant les propriétés du projet de la DLL.You can add the ROOTERLIB_EXPORTS symbol to the command line by using the project properties of the DLL.

    La classe CRooterLib déclare un constructeur et la méthode d'estimation SqareRoot.The CRooterLib class declares a constructor and the SqareRoot estimator method.

  4. Ajoutez le symbole ROOTERLIB_EXPORTS à la ligne de commande.Add the ROOTERLIB_EXPORTS symbol to the command line.

    1. Dans l’Explorateur de solutions, choisissez le projet RooterLib, puis Propriétés dans le menu contextuel.In Solution Explorer, choose the RooterLib project, and then choose Properties from the shortcut menu.

      Ajouter la définition d’un symbole de préprocesseurAdd a preprocessor symbol definition

    2. Dans la boîte de dialogue Page de propriétés de RooterLib, développez Propriétés de configuration, C++, puis choisissez Préprocesseur.In the RooterLib Property Page dialog box, expand Configuration Properties, expand C++ and choose Preprocessor.

    3. Choisissez <Modifier> dans la liste Définitions de préprocesseur, puis ajoutez ROOTERLIB_EXPORTS dans la boîte de dialogue Définitions de préprocesseur.Choose <Edit...> from the Preprocessor Definitions list, and then add ROOTERLIB_EXPORTS in the Preprocessor Definitions dialog box.

  5. Ajoutez des implémentations minimales des fonctions déclarées.Add minimal implementations of the declared functions. Ouvrez RooterLib.cpp, puis ajoutez le code suivant :Open RooterLib.cpp and add the following code:

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

Rendre les fonctions DLL visibles par le code de testMake the dll functions visible to the test code

  1. Ajoutez RooterLib au projet RooterLibTests.Add RooterLib to the RooterLibTests project.

    1. Dans l’Explorateur de solutions, choisissez le projet RooterLibTests, puis Références dans le menu contextuel.In Solution Explorer, choose the RooterLibTests project and then choose References... on the shortcut menu.

    2. Dans la boîte de dialogue Propriétés du projet RooterLib, développez Propriétés communes, puis choisissez Framework et références.On the RooterLib Project Properties dialog box, expand Common Properties and choose Framework and References.

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

    4. Dans la boîte de dialogue Ajouter une référence, développez Solution, puis choisissez Projets.In the Add Reference dialog box, expand Solution and then choose Projects. Sélectionnez ensuite l’élément RouterLib.Then select the RouterLib item.

  2. Incluez le fichier d’en-tête RooterLib dans unittest1.cpp.Include the RooterLib header file in unittest1.cpp.

    1. Ouvrez unittest1.cpp.Open unittest1.cpp.

    2. Ajoutez le code suivant sous la ligne #include "CppUnitTest.h" :Add this code to below the #include "CppUnitTest.h" line:

      #include "..\RooterLib\RooterLib.h"
      
  3. Ajoutez un test qui utilise la fonction importée.Add a test that uses the imported function. Ajoutez le code suivant à unittest1.cpp :Add the following code to unittest1.cpp:

    TEST_METHOD(BasicTest)
    {
        CRooterLib 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, dans le nœud Tests non exécutés.The new test appears in Test Explorer in the Not Run Tests node.

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

    Test de base réussiBasic 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)
    {
        CRooterLib rooter;
        for (double v = 1e-6; v < 1e6; v = v * 3.2)
        {
            double expected = v;
            double actual = rooter.SquareRoot(v*v);
            double tolerance = expected/1000;
            Assert::AreEqual(expected, actual, tolerance);
        }
    };
    

    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. Dans l'Explorateur de tests, choisissez Exécuter tout.In Test Explorer, choose Run All.

  3. Le test échoue.The 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.

  4. Améliorez le code testé afin que le nouveau test réussisse.Enhance the code under test so that the new test passes. Ajoutez ce qui suit à RooterLib.cpp :Add the following to RooterLib.cpp:

    #include <math.h>
    ...
    // Find the square root of a number.
    double CRooterLib::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;
    }
    
  5. 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.

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 à unittest1.cpp :Add another test to unittest1.cpp:

    // Verify that negative inputs throw an exception.
     TEST_METHOD(NegativeRangeTest)
     {
       wchar_t message[200];
       CRooterLib rooter;
       for (double v = -0.1; v > -3.0; v = v - 0.5)
       {
         try
         {
           // Should raise an exception:
           double result = rooter.SquareRoot(v);
    
           swprintf_s(message, L"No exception for input %g", v);
           Assert::Fail(message, LINE_INFO());
         }
         catch (std::out_of_range ex)
         {
           continue; // Correct exception.
         }
         catch (...)
         {
           swprintf_s(message, L"Incorrect exception for %g", v);
           Assert::Fail(message, LINE_INFO());
         }
       }
    };
    
  2. Dans l'Explorateur de tests, choisissez Exécuter tout.In Test Explorer, choose Run All.

    Le test échoue.The test fails. Sélectionnez le nom du test dans l'explorateur de tests.Choose the test name in Test Explorer. 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

  3. 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.

    3. Ajoutez du code à RooterLib.cpp pour intercepter l’exception :Add code to RooterLib.cpp to catch the exception:

      #include <stdexcept>
      ...
      double CRooterLib::SquareRoot(double v)
      {
          //Validate the input parameter:
          if (v < 0.0)
          {
            throw std::out_of_range("Can't do square roots of negatives");
          }
      ...
      
    4. Dans l’Explorateur de tests, choisissez Exécuter tout pour tester la méthode corrigée et vérifier que vous n’avez pas introduit une régression.In Test Explorer, choose Run All to test the corrected method and make sure that you haven't introduced a regression.

    Toutes les tests réussissent maintenant.All tests now pass.

    Tous les tests sont concluantsAll tests pass

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

  1. Simplifiez le calcul central dans 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. Choisissez Exécuter tout pour tester la méthode refactorisée et vérifier que vous n’avez pas introduit une régression.Choose Run All to test the refactored method and make sure that you haven't introduced a regression.

    Conseil

    Un ensemble stable de tests unitaires corrects est l'assurance que vous n'avez pas créé de bogues lors de la modification du code.A stable set of good 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.