Prise en main avec CppUTest

Oiseau

14 mai 2017

Email

1 min read

Prise en main avec CppUTest

Oiseau

14 mai 2017

Email

1 min read

Prise en main avec CppUTest

Chez SparkPost, nous consacrons beaucoup de temps et d'efforts à tester notre code. Notre plateforme est écrite en C, et récemment, j'ai cherché comment intégrer un framework de tests unitaires appelé « CppUTest », qui fournit des tests de style xUnit pour C/C++. Ce framework est robuste, riche en fonctionnalités et en cours de développement actif, ce qui en fait un excellent choix. Il fournit également une couche d'intégration en C qui a facilité son utilisation avec notre code C de plateforme, même si la plupart du framework est en C++. Ce tutoriel couvre comment commencer avec CppUTest sur vos propres projets.

Chez SparkPost, nous consacrons beaucoup de temps et d'efforts à tester notre code. Notre plateforme est écrite en C, et récemment, j'ai recherché l'intégration avec un framework de test unitaire appelé "CppUTest", qui fournit des tests de style xUnit pour C/C++. Ce framework est robuste, riche en fonctionnalités et en développement actif, ce qui en fait un excellent choix. Il offre également une couche d'intégration C qui rend son utilisation facile avec notre code C de plateforme même si la plupart du framework est en C++. Ce tutoriel couvre comment commencer avec CppUTest sur vos propres projets.

Téléchargement de CppUTest

La page du projet CppUTest est disponible ici, et le dépôt est sur github. Il est également inclus dans les dépôts de gestion de paquets pour de nombreuses distributions Linux, ainsi que homebrew sur Mac OS. Les exemples qui suivent ont été exécutés sur Mac OS X, mais ils sont dérivés de code écrit pour Red Hat, le système d'exploitation sur lequel notre plateforme fonctionne.

Les bases sont bien documentées sur la page d'accueil de CppUTest. Nous allons passer rapidement là-dessus et aborder quelques-unes des fonctionnalités plus intéressantes.

Poser les bases

Première chose, écrivons du code !

Notre projet de test aura un fichier 'main' et inclura une bibliothèque utilitaire appelée 'code'. La bibliothèque fournira une fonction simple qui renvoie 1 (pour le moment). Les fichiers seront organisés comme suit :

├── src │ ├── code │ │ ├── code.cpp │ │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Commençons par écrire les fichiers src/

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Maintenant, faisons les tests, qui vivront dans le répertoire t/.  La première chose à faire est de mettre en place un exécuteur de tests qui exécutera nos fichiers de test. C'est aussi la fonction ‘main’  qui s'exécutera une fois que tout aura été compilé :

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Nous pouvons maintenant écrire notre premier module de test :

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Ensuite, nous devons écrire les makefiles.  Nous en aurons besoin de deux : un pour les fichiers du projet sous src/, et un pour les tests.

Première chose, écrivons du code !

Notre projet de test aura un fichier 'main' et inclura une bibliothèque utilitaire appelée 'code'. La bibliothèque fournira une fonction simple qui renvoie 1 (pour le moment). Les fichiers seront organisés comme suit :

├── src │ ├── code │ │ ├── code.cpp │ │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Commençons par écrire les fichiers src/

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Maintenant, faisons les tests, qui vivront dans le répertoire t/.  La première chose à faire est de mettre en place un exécuteur de tests qui exécutera nos fichiers de test. C'est aussi la fonction ‘main’  qui s'exécutera une fois que tout aura été compilé :

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Nous pouvons maintenant écrire notre premier module de test :

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Ensuite, nous devons écrire les makefiles.  Nous en aurons besoin de deux : un pour les fichiers du projet sous src/, et un pour les tests.

Première chose, écrivons du code !

Notre projet de test aura un fichier 'main' et inclura une bibliothèque utilitaire appelée 'code'. La bibliothèque fournira une fonction simple qui renvoie 1 (pour le moment). Les fichiers seront organisés comme suit :

├── src │ ├── code │ │ ├── code.cpp │ │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Commençons par écrire les fichiers src/

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Maintenant, faisons les tests, qui vivront dans le répertoire t/.  La première chose à faire est de mettre en place un exécuteur de tests qui exécutera nos fichiers de test. C'est aussi la fonction ‘main’  qui s'exécutera une fois que tout aura été compilé :

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Nous pouvons maintenant écrire notre premier module de test :

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Ensuite, nous devons écrire les makefiles.  Nous en aurons besoin de deux : un pour les fichiers du projet sous src/, et un pour les tests.

Projet Makefile

Le makefile du projet sera au même niveau que les répertoires ‘src’ et ‘t’ à la racine du projet. Il devrait ressembler à ceci :

# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=example TEST_DIR=t test: make -C $(TEST_DIR) test_clean: make -C $(TEST_DIR) clean code.o: gcc -c -I$(CODE_DIR) $(CODE_DIR)/code.cpp -o $(CODE_DIR)/code.o main: code.o gcc -I$(CODE_DIR) $(CODE_DIR)/code.o $(SRC_DIR)/main.cpp -o $(OUT) all: test main clean: test_clean rm $(SRC_DIR)/*.o $(CODE_DIR)/*.o $(OUT)

Notez que cela utilise ‘make -C’ pour les cibles de test – ce qui signifie qu'il appellera à nouveau ‘make’ en utilisant le makefile dans le répertoire de test.

À ce stade, nous pouvons compiler le code ‘src’ avec le makefile et voir que cela fonctionne :

[]$ make main gcc -c -I./src/code ./src/code/code.cpp -o ./src/code/code.o gcc -I./src/code ./src/code/code.o ./src/main.cpp -o example []$ ./example bonjour monde !

Tests Makefile

Pour les tests, les choses sont un peu plus complexes car nous devons correctement charger et intégrer avec la bibliothèque CppUTest.

Le dépôt CppUTest fournit un fichier appelé “MakefileWorker.mk”. Il fournit de nombreuses fonctionnalités qui rendent la construction avec CppUTest simple. Le fichier se trouve dans le répertoire “build” dans le dépôt git. Pour ce tutoriel, nous allons supposer qu'il a été copié dans le répertoire ‘t/’. Il peut être utilisé comme suit :

# nous ne voulons pas utiliser de chemins relatifs, alors nous définissons ces variables PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # spécifiez où se trouvent le code source et les inclusions INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # spécifiez où se trouve le code de test TEST_SRC_DIRS = $(TEST_DIR) # nommer le binaire de test TEST_TARGET=example # où se trouve la bibliothèque cpputest CPPUTEST_HOME=/usr/local # exécuter MakefileWorker.mk avec les variables définies ici include MakefileWorker.mk

Notez que CPPUTEST_HOME doit être défini à l'endroit où CppUTest a été installé. Si vous avez installé un paquet de distribution, ce sera généralement sous /usr/local sur un système linux/mac. Si vous avez extrait le dépôt vous-même, c'est où cet extrait se trouve.

Toutes ces options sont documentées dans MakefileWorker.mk.

MakefileWorker.mk ajoute également quelques cibles de makefile, y compris les suivantes :

  1. all – construit les tests indiqués par le makefile

  2. clean – supprime tous les fichiers d'objet et gcov générés pour les tests

  3. realclean – supprime tous les fichiers d'objet ou gcov dans tout l'arborescence de répertoires

  4. flags – liste tous les drapeaux configurés utilisés pour compiler les tests

  5. debug – liste tous les fichiers source, objets, dépendances, et 'choses à nettoyer'

Code Coverage

Les tests unitaires ne seraient pas complets sans un rapport de couverture. L'outil incontournable pour cela pour les projets utilisant gcc est gcov, disponible dans la suite standard des utilitaires gcc. Cpputest s'intègre facilement avec gcov, tout ce que vous avez à faire est d'ajouter cette ligne au makefile :

CPPUTEST_USE_GCOV=Y

Ensuite, nous devons nous assurer que le script filterGcov.sh de ce dépôt se trouve dans ‘/scripts/filterGcov.sh’ par rapport à l'endroit où vous avez défini ‘CPPUTEST_HOME’. Il doit également avoir les permissions d'exécution.

Dans le Makefile d'exemple, il serait déployé à ‘/usr/local/scripts/filterGcov.sh’. Si vous exécutez CppUTest à partir d'un dépôt, tout devrait fonctionner sans modification.




Avec cela en place, vous pouvez simplement exécuter ‘make gcov’ et l'analyse sera générée pour vous. Dans notre cas, nous devrons ‘make -B’ pour reconstruire les fichiers objets avec gcov activé :

[]$ make -B gcov < compilation output > for d in /Users/ykuperman/code/blogpost/qa/src/code ; do \ FILES=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $FILES >> gcov_output.txt 2>>gcov_error.txt ; \ done for f in ; do \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ done /usr/local/scripts/filterGcov.sh gcov_output.txt gcov_error.txt gcov_report.txt example.txt cat gcov_report.txt 100.00% /Users/ykuperman/code/blogpost/qa/src/code/code.cpp mkdir -p gcov mv *.gcov gcov mv gcov_* gcov Voir le répertoire gcov pour les détails

Cela produira un certain nombre de fichiers dans un nouveau répertoire ‘gcov’. Ceux-ci sont :

  1. code.cpp.gcov – le fichier ‘gcov’ réel pour le code testé

  2. gcov_error.txt – un rapport d'erreur (dans notre cas, il devrait être vide)

  3. gcov_output.txt – la sortie réelle de la commande gcov exécutée

  4. gcov_report.txt – un résumé de la couverture pour chaque fichier testé

  5. gcov_report.txt.html – une version html de gcov_report

Cpputest Détection de fuite de mémoire

Cpputest vous permet de détecter automatiquement les fuites de mémoire en redéfinissant la famille standard des fonctions « malloc/free » pour utiliser ses propres enveloppes à la place. Cela lui permet de détecter rapidement les fuites et de les signaler pour chaque exécution de test. Cette fonction est activée par défaut dans MakefileWorker.mk, donc elle est déjà en marche avec les étapes décrites jusqu'à présent.

Pour illustrer, perdons un peu de mémoire dans test_func() !

Revenons à code.c, nous ajoutons un malloc()  à la fonction, comme ceci :

int test_func() { malloc(1); return 1; }

Maintenant, après recompilation, l'erreur suivante est produite :

test.cpp:9: error: Failure in TEST(AwesomeExamples, FirstExample) Memory leak(s) found. Alloc num (4) Leak size: 1 Allocated at: ./code.c and line: 6. Type: "malloc" Memory: <0x7fc9e94056d0> Content: 0000: 00 |.| Total number of leaks: 1

Ceci montre quel test a causé la fuite, où la fuite est survenue dans le code source, et ce qu'il y avait dans la mémoire perdue. Très utile !

Il y a quelques mises en garde avec cette fonctionnalité :

  1. Cpputest utilise des macros de préprocesseur pour redéfinir dynamiquement tous les appels aux fonctions de gestion de mémoire standard. Cela signifie que cela ne fonctionnera que pour les appels dans le code source testé, car c'est ce qui est compilé avec les surcharges de CppUTest. Les fuites dans les bibliothèques liées ne seront pas détectées.

  2. Parfois, la mémoire allouée pour toute la durée de vie du processus n'est pas censée être libérée. Cela peut générer de nombreuses erreurs bruyantes si vous testez un module avec ce comportement. Pour désactiver la détection des fuites, vous pouvez faire cela :

CPPUTEST_USE_MEM_LEAK_DETECTION=N

Intéressé par More ?

Ceci n'est que la partie émergée de l'iceberg en ce qui concerne toutes les fonctionnalités contenues dans cet outil. Outre les bases discutées ici, il comprend également un cadre de simulation, une couche d'intégration C directe et un cadre de plugin, pour n'en nommer que quelques-uns parmi les plus importants. Le repo contient également un répertoire complet de scripts d'assistance qui peuvent aider à automatiser certaines des parties routinières du travail avec le framework.

J'espère que les informations ici vous aideront à améliorer la qualité de votre code C/C++ avec cet excellent outil !

Connectons-vous avec un expert Bird.
Découvrez toute la puissance du Bird en 30 minutes.

En soumettant, vous acceptez que Bird puisse vous contacter au sujet de nos produits et services.

Vous pouvez vous désabonner à tout moment. Consultez la Déclaration de confidentialité de Bird pour plus de détails sur le traitement des données.

Company

Newsletter

Restez à jour avec Bird grâce aux mises à jour hebdomadaires dans votre boîte de réception.

Connectons-vous avec un expert Bird.
Découvrez toute la puissance du Bird en 30 minutes.

En soumettant, vous acceptez que Bird puisse vous contacter au sujet de nos produits et services.

Vous pouvez vous désabonner à tout moment. Consultez la Déclaration de confidentialité de Bird pour plus de détails sur le traitement des données.

Company

Newsletter

Restez à jour avec Bird grâce aux mises à jour hebdomadaires dans votre boîte de réception.

Connectons-vous avec un expert Bird.
Découvrez toute la puissance du Bird en 30 minutes.

En soumettant, vous acceptez que Bird puisse vous contacter au sujet de nos produits et services.

Vous pouvez vous désabonner à tout moment. Consultez la Déclaration de confidentialité de Bird pour plus de détails sur le traitement des données.

R

Atteindre

G

Grow

M

Manage

A

Automate

Company

Newsletter

Restez à jour avec Bird grâce aux mises à jour hebdomadaires dans votre boîte de réception.