table des matières
- Projets C et C++
- Projets MbedOS
- TU avec Laravel
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.
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 choisirExécuter
- Si votre configuration est valide, vous obtenez le résultat suivant
Modifier la valeur d’une assertion (par exemple
REQUIRE( Factorial(2) == 1 );
)
- Observer le résultat
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
- Ajouter le chemin vers le fichier.cpp de votre projet ($SRCS2)
- Ajouter le chemin vers le fichier.cpp de votre projet ($HEADERS2)
- Créer un fichier de TU dans le répertoire test
- 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
- 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
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 lemain()
du programme principal pour ne pas générer plusieurs fonctionmain()
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 avecpython 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 :
- Plusieurs tests cases dans un seul fichier
- Utilisation d’un objet matériel (DigitalOut)
- Utilisation de
printf()
etsleep()
- 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 lemain()
- 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 TUCase 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
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 :
- mbed-greentea-testing/TESTS/integration/test_case/main.cpp (pour la carte)
- mbed-greentea-testing/TESTS/host_tests/hello_world_tests.py (pour le PC)
- Le déroulement du test est le suivant :
- La carte envoi la string
hello
au pc - Le PC répond
world
. - Si le PC a bien répondu
world
, le test est validé
- La carte envoi la string
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
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