Effectuer des tests unitaires à l’aide de GoogleTest et CTest


  • FrançaisFrançais


  • Cet article fait suite à mon dernier article Configurer un système de build avec CMake et VSCodium.

    Dans le dernier article, j’ai montré comment configurer un système de construction basé sur VSCodium et CMake. Cet article affine cette configuration en intégrant des tests unitaires significatifs à l’aide de GoogleTest et CTest.

    Si ce n’est déjà fait, clonez le dépôt, ouvrez-le dans VSCodium et vérifiez le tag devops_2 en cliquant sur le principale-symbole de la branche (marqueur rouge) et choix de la branche (marqueur jaune) :

    Sinon, ouvrez la ligne de commande et tapez :

    $ git checkout tags/devops_2

    GoogleTest

    GoogleTest est un framework de test C++ open source indépendant de la plate-forme. Même si GoogleTest n’est pas destiné exclusivement aux tests unitaires, je vais l’utiliser pour définir les tests unitaires pour le Générateur bibliothèque. En général, un test unitaire doit vérifier le comportement d’une seule unité logique. le Générateur la bibliothèque est une unité, donc je vais écrire quelques tests significatifs pour assurer le bon fonctionnement.

    Avec GoogleTest, les cas de test sont définis par des macros d’assertions. Le traitement d’une assertion génère l’un des résultats suivants :

    • Succès: Test réussi.
    • Échec non fatal: Le test a échoué, mais la fonction de test continuera.
    • Échec fatal: Le test a échoué et la fonction de test sera abandonnée.

    Les macros d’assertions suivent ce schéma pour distinguer une défaillance fatale d’une défaillance non fatale :

    • ASSERT_* défaillance fatale, la fonction est annulée.
    • EXPECT_* échec non fatal, la fonction n’est pas interrompue.

    Google recommande d’utiliser EXPECT_* macros car elles permettent au test de continuer lorsque les tests définissent plusieurs assertions. Une macro d’assertion prend deux arguments : le premier argument est le nom du groupe de test (une chaîne librement sélectionnable) et le second argument est le nom du test lui-même. le Générateur bibliothèque définit juste la fonction produire(…), donc les tests de cet article appartiennent au même groupe : GénérateurTest.

    Les tests unitaires suivants pour le produire(…) fonction se trouve dans GeneratorTest.cpp.

    Vérification des références

    le produire(…) fonction prend une référence à un std ::stringstream en argument et renvoie la même référence. Le premier test consiste donc à vérifier si la référence passée est la même référence que celle renvoyée par la fonction.

    TEST(GeneratorTest, ReferenceCheck){
        const int NumberOfElements = 10;
        std::stringstream buffer;
        EXPECT_EQ(
            std::addressof(buffer),
            std::addressof(Generator::generate(buffer, NumberOfElements))
        );
    }

    Ici j’utilise std :: adressede pour vérifier si l’adresse de l’objet renvoyé fait référence au même objet que j’ai fourni en entrée.

    Nombre d’éléments

    Ce test vérifie si le nombre d’éléments dans la référence stringstream correspond au nombre donné en argument.

    TEST(GeneratorTest, NumberOfElements){
        const int NumberOfElements = 50;
        int nCalcNoElements = 0;

        std::stringstream buffer;

        Generator::generate(buffer, NumberOfElements);
        std::string s_no;

        while(std::getline(buffer, s_no, ' ')) {
            nCalcNoElements++;
        }

        EXPECT_EQ(nCalcNoElements, NumberOfElements);
    }

    Mélanger

    Ce test vérifie le bon fonctionnement du moteur aléatoire. Si j’invoque le produire fonction deux fois de suite, je m’attends à ne pas obtenir le même résultat.

    TEST(GeneratorTest, Shuffle){

        const int NumberOfElements = 50;

        std::stringstream buffer_A;
        std::stringstream buffer_B;

        Generator::generate(buffer_A, NumberOfElements);
        Generator::generate(buffer_B, NumberOfElements);

        EXPECT_NE(buffer_A.str(), buffer_B.str());
    }

    Somme de contrôle

    C’est le plus grand test. Il vérifie si la somme des chiffres d’une série numérique de 1 à n est le même que la somme de la série de sortie mélangée. Je m’attends à ce que la somme corresponde à la produire(…) La fonction devrait simplement créer une variante mélangée d’une telle série.

    TEST(GeneratorTest, CheckSum){

        const int NumberOfElements = 50;
        int nChecksum_in = 0;
        int nChecksum_out = 0;

        std::vector<int> vNumbersRef(NumberOfElements); // Input vector
        std::iota(vNumbersRef.begin(), vNumbersRef.end(), 1); // Populate vector

        // Calculate reference checksum
        for(const int n : vNumbersRef){
            nChecksum_in += n;
        }

        std::stringstream buffer;
        Generator::generate(buffer, NumberOfElements);

        std::vector<int> vNumbersGen; // Output vector
        std::string s_no;

        // Read the buffer back back to the output vector
        while(std::getline(buffer, s_no, ' ')) {
            vNumbersGen.push_back(std::stoi(s_no));
        }

        // Calculate output checksum
        for(const int n : vNumbersGen){
            nChecksum_out += n;
        }

        EXPECT_EQ(nChecksum_in, nChecksum_out);
    }

    Les tests ci-dessus peuvent également être débogués comme une application C++ ordinaire.

    CTest

    En plus du test unitaire dans le code, le CTest L’utilitaire me permet de définir des tests pouvant être effectués sur des exécutables. En un mot, j’appelle l’exécutable avec certains arguments et fais correspondre la sortie avec expressions régulières. Cela me permet simplement de vérifier le comportement de l’exécutable avec des arguments de ligne de commande incorrects. Les tests sont définis au niveau supérieur CMakeLists.txt. Voici un examen plus approfondi de trois cas de test :

    Utilisation régulière

    Si un entier positif est fourni comme argument de ligne de commande, je m’attends à ce que l’exécutable produise une série de nombres séparés par des espaces :

    add_test(NAME RegularUsage COMMAND Producer 10)
    set_tests_properties(RegularUsage
        PROPERTIES PASS_REGULAR_EXPRESSION "^[0-9 ]+"
    )

    Pas de dispute

    Si aucun argument n’est fourni, le programme doit se fermer immédiatement et afficher la raison :

    add_test(NAME NoArg COMMAND Producer)
    set_tests_properties(NoArg
        PROPERTIES PASS_REGULAR_EXPRESSION "^Enter the number of elements as argument"
    )

    Mauvais argument

    Fournir un argument qui ne peut pas être converti en entier devrait également provoquer une sortie immédiate avec un message d’erreur. Ce test fait appel à la Producteur exécutable avec le paramètre de ligne de commande“ABC”:

    add_test(NAME WrongArg COMMAND Producer ABC)
    set_tests_properties(WrongArg
        PROPERTIES PASS_REGULAR_EXPRESSION "^Error: Cannot parse"
    )

    Tester les épreuves

    Pour exécuter un seul test et voir comment il est traité, appelez ctest depuis la ligne de commande en fournissant les arguments suivants :

    • Exécutez un seul tst : -R <test-name>
    • Activer la sortie détaillée : -VV

    Voici la commande ctest -R Usage -VV:

    $ ctest -R Usage -VV
    UpdatecTest Configuration from :/home/stephan/Documents/cpp_testing sample/build/DartConfiguration.tcl
    UpdateCTestConfiguration from :/home/stephan/Documents/cpp_testing sample/build/DartConfiguration.tcl
    Test project /home/stephan/Documents/cpp_testing sample/build
    Constructing a list of tests
    Done constructing a list of tests
    Updating test list for fixtures
    Added 0 tests to meet fixture requirements
    Checking test dependency graph...
    Checking test dependency graph end

    Dans ce bloc de code, j’ai appelé un test nommé Usage.

    Cela a exécuté l’exécutable sans arguments de ligne de commande :

    test 3
        Start 3: Usage
    3: Test command: /home/stephan/Documents/cpp testing sample/build/Producer

    Le test a échoué car la sortie ne correspond pas à l’expression régulière [^[0-9]+].

    3: Enter the number of elements as argument
    1/1 test #3. Usage ................

    Failed Required regular expression not found.
    Regex=[^[0-9]+]

    0.00 sec round.

    0% tests passed, 1 tests failed out of 1
    Total Test time (real) =
    0.00 sec
    The following tests FAILED:
    3 - Usage (Failed)
    Errors while running CTest
    $

    Pour exécuter tous les tests (y compris celui défini avec GoogleTest), accédez au construire répertoire et exécuter ctest:

    Dans VSCodium, cliquez sur la zone marquée en jaune dans la barre d’informations pour appeler CTest. Si tous les tests réussissent, la sortie suivante s’affiche :

    Automatisez les tests avec Git Hooks

    Désormais, l’exécution des tests est une étape supplémentaire pour le développeur. Le développeur peut également valider et envoyer du code qui ne réussit pas les tests. Grâce à Crochets Git, je peux implémenter un mécanisme qui exécute automatiquement les tests et empêche le développeur de commettre accidentellement du code défectueux.

    Aller vers .git/hooks, créez un fichier vide nommé pré-commit, puis copiez et collez le code suivant :

    #!/usr/bin/sh

    (cd build; ctest --output-on-failure -j6)

    Après cela, rendez ce fichier exécutable :

    $ chmod +x pre-commit

    Ce script invoque CTest lors de la tentative d’exécution d’un commit. Si un test échoue, comme dans la capture d’écran ci-dessous, le commit est abandonné :

    Si les tests réussissent, le commit est traité et le résultat ressemble à ceci :

    Le mécanisme décrit n’est qu’une barrière souple : un développeur peut toujours commettre un code défectueux en utilisant git commit --no-verify. Je peux m’assurer que seul le code fonctionnel est poussé en configurant un serveur de build. Ce sujet fera partie d’un article séparé.

    Résumé

    Les techniques mentionnées dans cet article sont faciles à mettre en œuvre et vous aident à trouver rapidement des bogues dans votre code. L’utilisation de tests unitaires améliorera probablement la qualité de votre code et, comme je l’ai montré, le fera sans perturber votre flux de travail. Le framework GoogleTest fournit des fonctionnalités pour tous les scénarios imaginables ; Je n’ai utilisé qu’un sous-ensemble de ses fonctionnalités. À ce stade, je veux également mentionner le Introduction à GoogleTest, qui vous donne un aperçu des idées, des opportunités et des fonctionnalités du framework.

    Source

    La Rédaction

    L'équipe rédactionnnelle du site

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Copy code