table des matières
  1. Projets C et C++
    1. Organisation du projet modèle
    2. Fichiers importants pour l’élaboration et l’exécution des TU
      1. CMakelist principal
      2. Fichier test/CMakelist.txt
      3. Fichier test/testProjet.cpp
    3. Compilation et exécution des TU
    4. Création des TU avec Catch2, bonnes pratiques
      1. Pour chaque classe de votre projet à tester
    5. Quelques ASSERTIONS utiles de Catch2
      1. REQUIRE
      2. Matchers et REQUIRE_THAT
    6. Conclusion
  2. Projets MbedOS
    1. Sitographie
    2. TU avec MbedOs - préalables et limitations
      1. Enlever le main principal pour les tests
      2. Mettre à jour les paquets mbed
    3. Projet Modèle
    4. Compilation (sans tests)
    5. Le projet et ses tests
    6. Description de l’exemple 1- tests-test-group-simple-test
      1. Détails du fichier main.cpp
      2. Lister les TU du fichier
      3. Fonction simple_test()
      4. fonction gyro_test()
      5. Lancer le(s) test(s)
      6. Créer un rapport au format html
    7. Description de l’exemple 2- tests-integration-test_case
      1. Lancer le test
    8. Quelques commandes supplémentaires
  3. TU avec Laravel
    1. Sitographie
    2. Organisation
    3. Exécuter les tests

La création de tests unitaires est une étape indispensable au développement de logiciels de qualité.

  • Chaque module d’un logiciel doit être validé par un test unitaire (TU).
  • Dans la pratique, cela consiste à valider chaque méthode de vos classes, manuellement ou automatiquement.
  • Aujourd’hui, des frameworks de TU nous permettent d’automatiser l’écriture et l’exécution des TU au long du projet. Les TU doivent être réalisés pour chaque module et tous les TU doivent être conservés lors du développement des futurs modules pour éviter les régressions du logiciel.

  • Lorsque cela est possible, les TU sont exécutés sur le PC de développement et par le pipeline CI/CD lors des commits sur le dépôt du projet.

  • Dans le document ci-dessous, nous décrirons la mise en oeuvre de frameworks adaptés aux langages de programmations étudiés dans le BTS afin que vous intégriez les TU dans vos projets.

Attention : Pour les langages compilés (comme le C et le C++), les TU génèrent un exécutable spécifique grâce à un deuxième main(). Il ne faut donc pas compiler le programme principal et les TU en même temps, sinon vous aurez une erreur à l’étape d’édition des liens (linkage ou ld en anglais)

Projets C et C++

Le framework de TU choisi dans la formation est Catch2

Il existe plusieurs méthodes pour l’installer sur vos postes, la méthode choisie ici est de faire appel à une dépendance lors de la création de vos projets via l’outil conan.

Le projet Modèle C++ fourni sur le dépôt de la section est configuré pour que les TU soit fonctionnels immédiatement sans configuration supplémentaire de votre part.

Organisation du projet modèle

Après avoir cloné le projet et ouvert celui-ci dans QT6.x.x, voici l’arborescence de votre projet.

QT tu

Fichiers importants pour l’élaboration et l’exécution des TU

CMakelist principal

Le CMakelist principal contient les directives indiquant à CMake qu’il y a un autre dossier (test) qui contient les tests. Si vous ne voulez pas que votre projet gère les TU (ce qui n’est pas le but ;O), commentez la partie du code ci-dessous dans votre fichier CMakelist.txt

# Options ######################################################################
option(BUILD_TESTS "Enable to build unit tests" TRUE)

# Testing ######################################################################
if (BUILD_TESTS)
    # Coverage #################################################################
    set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake-modules)
    include(CodeCoverage)
    append_coverage_compiler_flags()
    target_link_libraries(${CMAKE_PROJECT_NAME} gcov)
    setup_target_for_coverage_gcovr_html(
        NAME coverage
        EXECUTABLE ${CMAKE_BINARY_DIR}/test/bin/testProjet
        DEPENDENCIES ${CMAKE_PROJECT_NAME}
        EXCLUDE "build/CMakeFiles/*")

    enable_testing ()
    add_subdirectory(test)
endif(BUILD_TESTS)

Fichier test/CMakelist.txt

cmake_minimum_required(VERSION 3.0.2)
project(testProjet)

#1/3-Ajouter les dossiers/fichiers.cpp du projet
set(SRCS2
#    ${CMAKE_SOURCE_DIR}/Dossier/file.cpp
    )

#2/3-Ajouter les dossiers/fichiers.h du projet
set(HEADERS2
#    ${CMAKE_SOURCE_DIR}/Client_parking/client_parking.h
    )

#3/3-Ajouter les dossiers/fichiers.cpp des tests unitaires
set(TST_SRCS
    testProjet.cpp
    )

include(CTest)
include(${CMAKE_BINARY_DIR}/conan-dependencies/conanbuildinfo.cmake)
conan_basic_setup(TARGETS)
add_executable(${PROJECT_NAME} ${TST_SRCS} ${SRCS2} ${HEADERS2})
target_link_libraries(${PROJECT_NAME} CONAN_PKG::catch2)
add_test(NAME testPro COMMAND ${PROJECT_NAME})

Dans la configuration par défaut fourni, le projet contient un seul fichier de TU (testProjet.cpp) indiqué à la ligne TST_SRCS du fichier.

L’exécutable est généré à partir de cette variable ainsi que des classes éventuelles de votre projet (variables $SRCS2 et $HEADERS2)

add_executable(${PROJECT_NAME} ${TST_SRCS} ${SRCS2} ${HEADERS2})

Fichier test/testProjet.cpp

#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file

#include <catch2/catch.hpp>

//Dans le cas d'un projet C, déclarer les librairies avec extern "C"
//extern "C" {

//}

unsigned int Factorial( unsigned int number ) {
    return number <= 1 ? number : Factorial(number-1)*number;
}

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(10) == 3628800 );
}

Le fichier testProjet.cpp créé à partir de l’exemple officiel de Catch2 met en oeuvre un TU minimal. Voici les explications détaillées concernant ce fichier.

  • Indiquer à Catch de fournir un main()

Attention : Quand vous aurez plusieurs fichiers contenant des TU, cette ligne ne doit être présente qu’une seule fois ! (sinon, vous générez plusieurs main())

#define CATCH_CONFIG_MAIN 
  • Inclure les bibliothèques du framework Catch2
#include <catch2/catch.hpp>
  • Inclure des bibliothèques C
extern "C" {

}

Si vous travaillez en C il faut déclarer vos bibliothèques en extern

  • Une fonction Factorial() de test
unsigned int Factorial( unsigned int number ) {
    return number <= 1 ? number : Factorial(number-1)*number;
}

Il s’agit d’une fonction factorielle qui va permettre de valider le fonctionnement des TU. Une fois la validation faite, il n’est pas nécessaire de la conserver.

  • Un TEST_CASE
TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(10) == 3628800 );
}

Ce TEST_CASE sert également à valider votre configuration et à vous donner un exemple. A supprimer après validation.

Compilation et exécution des TU

  • Pour compiler et exécuter les TU, faire un click droit sur testProjet puis choisir Exécuter
  • Si votre configuration est valide, vous obtenez le résultat suivant

TU OK

Modifier la valeur d’une assertion (par exemple REQUIRE( Factorial(2) == 1 );)

  • Observer le résultat
TU NOK

TU NOK

Catch2 indique que l’un des TEST_CASE a échoué, qu’une des ASSERTIONS a échoué et même le résultat attendu et celui obtenu. 2 == 1

Création des TU avec Catch2, bonnes pratiques

Maintenant que nous avons regardé le fonctionnement global, suivez ces bonnes pratiques pour vos TU

Pour chaque classe de votre projet à tester

  • Dans le fichier test/Cmakelist.txt
    1. Ajouter le chemin vers le fichier.cpp de votre projet ($SRCS2)
    2. Ajouter le chemin vers le fichier.cpp de votre projet ($HEADERS2)
    3. Créer un fichier de TU dans le répertoire test
    4. Ajouter le chemin vers ce fichier ($TST_SRCS)
  • Exemple pour une classe Voiture
#Ajouter les dossiers/fichiers.cpp du projet
set(SRCS2
    ${CMAKE_SOURCE_DIR}/Voiture/voiture.cpp
    )
#Ajouter les dossiers/fichiers.h du projet
set(HEADERS2
    ${CMAKE_SOURCE_DIR}/Voiture/voiture.h
    )

#Ajouter les dossiers/fichiers.cpp des tests unitaires
set(TST_SRCS
    TestVoiture.cpp
    )
  • Dans le fichier TestVoiture.cpp
    1. Ajouter vos TU
  • Exemple de TU

Ce TU teste le constructeur par défaut de la classe, le constructeur surchargé ainsi que les getters et setters

#include "voiture.h"

TEST_CASE("Voiture test", "[voiture]")
{
    Voiture voiture1;
    REQUIRE(voiture1.getMarque() == "Marque_defaut");
    REQUIRE(voiture1.getModele() == "Modele_defaut");
    REQUIRE(voiture1.getImmatriculation() == "XX-XXX-XX");

    Voiture voiture2("Porsche", "Cayenne", "AA-007-XD");
    REQUIRE(voiture2.getMarque() == "Porsche");
    REQUIRE(voiture2.getModele() == "Cayenne");
    REQUIRE(voiture2.getImmatriculation() == "AA-007-XD");

    voiture2.setMarque("Peugeot");
    voiture2.setModele("404");
    voiture2.setImmatriculation("CC-000-BB");
    REQUIRE(voiture2.getMarque() == "Peugeot");
    REQUIRE(voiture2.getModele() == "404");
    REQUIRE(voiture2.getImmatriculation() == "CC-000-BB");
}

Quelques ASSERTIONS utiles de Catch2

Référence Catch2

Cette section est en cours de développement, nous résumons ici les ASSERTIONS de Catch2 utilisées dans nos projets. Elle sera abondée en fonction de vos usages.

REQUIRE

  • Teste si une assertion est vrai
    REQUIRE( expression ) 
    
  • Exemple
    REQUIRE( Factorial(1) == 1 );
    

Matchers et REQUIRE_THAT

Les Matchers sont utilisés pour des besoins plus complexes comme pour savoir si une expression commence par..., contient... ou termine par...

  • Teste si une string termine par une sub string
    using Catch::Matchers::EndsWith;
    REQUIRE_THAT( getSomeString(), EndsWith("as a service") );
    
  • Exemple très utile pour tester la surdéfinition de l’opérateur ostream
using Catch::Matchers::EndsWith;
using Catch::Matchers::Contains;

TEST_CASE("TU Classe Badge operateur <<", "[Badge]")
{
  Badge badge1, badge2;
  Date date_debut;

  std::stringstream ss;
  ss << badge1.getDebutValidite();
  std::string dateDebut = ss.str();
  REQUIRE_THAT(dateDebut, EndsWith("1/1/1900") );

  ss << badge2;
  std::string badge = ss.str();
  REQUIRE_THAT(badge, Contains("1 7 221 87 161") );
}

Conclusion

Si votre pipeline est correctement configuré pour Gitlab CI/CD, les TU seront automatiquement exécutés lors de chaque commit.

Projets MbedOS

Sitographie

Mbed TU with greentea htrun unity utest

TU avec MbedOs - préalables et limitations

Les TU sont également possibles lors de développement de projet embarqué sur les cartes supportant MbedOS. Par contre, il y a plusieurs limitations et conditions préalables :

  • Les tests s’exécutent sur la carte embarquée, et donc les TU ne sont pas exploitables avec une chaîne CI/CD
  • les tests utilisent le programmateur STLink, ce qui ne correspond à la configuration actuelle de notre solution.
  • Il faut ajouter la directive #if !MBED_TEST_MODE dans le main() du programme principal pour ne pas générer plusieurs fonction main()
  • mbed-cli et les outils associés doivent être à jour

Remarque : Il faudra donc soit utiliser une autre carte (STLINK) pour les TU, soit reprogrammer le firmware de la carte lors des TU.

Enlever le main principal pour les tests

#if !MBED_TEST_MODE
int main() {
// ...
}
#endif

Mettre à jour les paquets mbed

  • Vérifier que les paquets de mbed-cli sont bien aux dernières versions disponibles et les mettre à jour si ce n’est pas le cas.

  • mbed-greentea notamment doit être en version 1.8.13 (bugs avec python 3.9 et la version 1.7.*)

  • Vérification
    $ pip3 list | grep mbed
    mbed-cli            1.10.5
    mbed-cloud-sdk      2.2.0
    mbed-flasher        0.10.1
    mbed-greentea       1.8.13
    mbed-host-tests     1.8.13
    mbed-ls             1.8.13
    mbed-os-tools       1.8.13
    
  • Mise à jour éventuelle des paquets
    python3 -m pip install -U mbed-greentea
    

Projet Modèle

Un projet Mbed6 incluant les TU est disponible sur notre Gitlab pour une prise en main rapide. Mbed greentea TU

Compilation (sans tests)

Avec une carte STLINK, le projet VsCode ne pourra pas compiler graphiquement. Il faut donc exécuter la ligne de commande suivante pour compiler et téléverser le programme sur la carte L’option -m auto permet de détecter automatiquement le modèle de la carte connectée en Usb

mbed compile -t GCC_ARM -m auto -f

Le projet et ses tests

Le projet reprend les exemples du site officiel avec quelques ajouts

Les tests sont dans le dossier TESTS.

Il y a deux exemples disponibles :

  • Le premier est une succession de tests cases simples (4) pour valider les éléments suivants :
    1. Plusieurs tests cases dans un seul fichier
    2. Utilisation d’un objet matériel (DigitalOut)
    3. Utilisation de printf() et sleep()
    4. Utilisation d’une classe personnelle (Gyrophare)
  • Le second est la mise en oeuvre de l’exemple officiel avec une communication entre la carte et le host (PC)

Description de l’exemple 1- tests-test-group-simple-test

Le TU est dans le dossier mbed-greentea-testing/TESTS/test-group/simple-test/main.cpp

Détails du fichier main.cpp

  • Créer l’objet specification et lancer les tests dans le main()
  • Le deuxième paramètre de specification correspond à un tableau contenant la liste des tests
Specification specification(greentea_setup, cases);

int main()
{
    return !Harness::run(specification);
}

Lister les TU du fichier

  • Chaque TU listé ici sera une fonction (comme simple_test() )contenant les TU
    Case cases[] = {
      Case("simple test", simple_test),
      Case("another test", another_test),
      Case("blink test", blink_test),
      Case("blink gyro test", gyro_test)
    };
    

Fonction simple_test()

Cet exemple vérifie que 2*2 = 4 (;O) grâce à la macro TEST_ASSERT_EQUAL()

MbedOs utilise unity et utest. Les macros utilisables sont disponibles sur le github de unity

L’instruction return CaseNext; exécutera automatiquement le prochain test de la liste

  • exemple 1
#include "utest/utest.h"
#include "unity/unity.h"
#include "greentea-client/test_env.h"


static control_t simple_test(const size_t call_count)
{
    /* test content here */
    TEST_ASSERT_EQUAL(4, 2 * 2);

    return CaseNext;
}

fonction gyro_test()

Ce TU exploite une classe Gyrophare développée par nos soins. Nous avons ajouté l’exemple pour valider les TU sur une classe personnelle contenant un élément matériel (une Led).

Comme le TU est exécuté sur la carte matérielle, les commandes pour allumer et éteindre le gyrophare actionnent réellement la led lors de l’exécution.

Les printf() sont également accessibles via le terminal, et le thread est mis en sommeil pendant 1s

#include "gyrophare.h"

static control_t gyro_test(const size_t call_count)
{
    printf("je tente d'allumer le gyro\n");
    Gyrophare monGyro(LED2);
    monGyro.allumer();
    TEST_ASSERT_TRUE(monGyro.getState()==1);
    ThisThread::sleep_for(1s);
    printf("maintenant on eteint le gyro\n");
    monGyro.eteindre();
    TEST_ASSERT_TRUE(monGyro.getState()==0);

    return CaseNext;
}

Lancer le(s) test(s)

Tous les TU peuvent être exécutés en ligne de commande sans préciser quel TU spécifique exécuter. Cette commande est déconseillée car tous les TU de MbedOs contenus dans le dossier mbed-os seront alors lancés.

  • Il vaut mieux préciser le TU à lancer grâce à l’option -n nom-du-test
  • Le nom du test est défini en fonction du chemin vers le fichier main.cpp contenant le TU.
mbed test -t GCC_ARM -m auto -v -n tests-test-group-simple-test

Créer un rapport au format html

Pour tous les tests compilés, il est possible de générer un rapport html avec la commande suivante

mbed test --report-html html_report.html --run

Le rapport est généré dans le fichier html_report.html

html report

Description de l’exemple 2- tests-integration-test_case

Le test élabore un scénario de communication entre le PC hôte et la carte mbed.

  • 2 fichiers sont nécessaires pour ce test :
    1. mbed-greentea-testing/TESTS/integration/test_case/main.cpp (pour la carte)
    2. mbed-greentea-testing/TESTS/host_tests/hello_world_tests.py (pour le PC)
  • Le déroulement du test est le suivant :
    1. La carte envoi la string hello au pc
    2. Le PC répond world.
    3. Si le PC a bien répondu world, le test est validé

Lancer le test

mbed test -t GCC_ARM -m auto -v -n tests-integration-test_case

Quelques commandes supplémentaires

  • Detecter la carte
    mbedls (ou mbed detect)
    
  • Connaître la liste des tests prêt à être exécutés (c’est à dire qui ont déjà été compilé)
    mbed test --run-list
    

TU avec Laravel

Sitographie

Tester avec Laravel

Organisation

Un projet Laravel contient dès l’installation du framework les outils nécessaires aux TU. Si la chaîne CICD est correctement configuré, les tests seront également exécutés sur le dépôt Gitlab.

Un exemple de TU est proposé par défaut dans le fichier tests/Unit/ExampleTest.php

<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function test_that_true_is_true()
    {
        $this->assertTrue(true);
    }
}

Un exemple de test de fonctionnalité (Feature) est proposé par défaut dans le fichier tests/Feature/ExampleTest.php

<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

Exécuter les tests

Pour exécuter les tests, lancer la commande :

vendor/bin/phpunit

ou

php artisan test

résultat test