Démarrer avec CppUTest

Démarrer avec CppUTest

Démarrer avec CppUTest

May 14, 2017

Publié par

Publié par

Bird

Bird

-

Catégorie :

Catégorie :

Courriel :

Courriel :

Ready to see Bird
in action?

Ready to see Bird
in action?

Getting Started with CppUTest

Chez SparkPost, nous consacrons beaucoup de temps et d'efforts à tester notre code. Notre plateforme est écrite en C, et j'ai récemment cherché à intégrer un cadre de test unitaire appelé "CppUTest", qui fournit des tests de type xUnit pour C/C++. Ce cadre est robuste, riche en fonctionnalités et en développement actif, ce qui en fait un excellent choix. Il fournit également une couche d'intégration C, ce qui le rend facile à utiliser avec le code C de notre plateforme, même si la majeure partie du framework est en C++. Ce tutoriel explique comment démarrer avec CppUTest sur vos propres projets.


Téléchargement de CppUTest

Le CppUTest project page is available here, and the repository is on github. It’s also included in the package management repositories for many linux distros, as well as homebrew on Mac OS. Le examples that follow were executed on Mac OS X, but they’re derived from code written for Red Hat, the OS our platform runs on.

The basics are well documented on CppUTest’s page d'accueil. We’re going to breeze through that and get to some of the more interesting features.


Poser les bases

Tout d'abord, é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 l'instant). Les fichiers seront disposés de la manière suivante :

├── 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

Now, let’s do the tests, which will live in the t/ directory.  The first thing to do is to set up a test runner which will run our test files. This is also the ‘main’  function that will execute once this is all compiled:

// 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); }

Next, we need to write makefiles.  We’ll need two: one for the project files under src/, and one for the tests.


Fichier Makefile du projet

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

# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=exemple 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)

Note that this uses ‘make -C’  for the test targets – meaning that it will call ‘make’  again using the makefile in the test directory.

A ce stade, nous pouvons compiler le code 'src' avec le makefile et voir qu'il 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 hello world !


Tests Makefile

Pour les tests, les choses sont un peu plus compliquées puisque nous devons charger et intégrer correctement la bibliothèque CppUTest.

Le dépôt de CppUTest fournit un fichier appelé "MakefileWorker.mk". Il fournit de nombreuses fonctionnalités qui rendent la construction avec CppUTest simple. Ce fichier se trouve dans le répertoire "build" du 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, donc nous définissons ces variables PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # spécifie où le code source et les includes sont situés INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # spécifier où se trouve le code de test TEST_SRC_DIRS = $(TEST_DIR) # comment appeler le binaire de test TEST_TARGET=exemple # 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 paquetage distro, ce sera typiquement sous /usr/local sur un système linux/mac. Si vous avez extrait le dépôt par vous-même, ce sera là où se trouve l'extraction.

Ces options sont toutes documentées dans MakefileWorker.mk.

MakefileWorker.mk ajoute également quelques cibles makefile, dont les suivantes :

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

  2. clean - supprime tous les fichiers d'objets et de gcov générés pour les tests

  3. realclean - supprime tout fichier objet ou gcov dans toute l'arborescence des répertoires

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

  5. debug - liste tous les fichiers sources, les objets, les dépendances et les "trucs à nettoyer".


Couverture du code

Unit testing would not be complete without a coverage report. The go-to tool for this for projects using gcc is gcov, available as part of the standard suite of gcc utilities. Cpputest integrates easily with gcov, all you need to do is add this line à la makefile:

CPPUTEST_USE_GCOV=Y

Next, we need to make sure that the filterGcov.sh script from ce repo is in ‘/scripts/filterGcov.sh’ relative to wherever you set ‘CPPUTEST_HOME’ to be. It also needs to have execute perms.

Dans l'exemple de Makefile, il serait déployé dans '/usr/local/scripts/filterGcov.sh'. Si vous exécutez CppUTest à partir d'un repo checkout, tout devrait fonctionner sans modification.


Avec cela en place, vous pouvez simplement lancer 'make gcov' et l'analyse sera générée pour vous. Dans notre cas, nous aurons besoin de '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 See gcov directory for details

Cela va produire un certain nombre de fichiers dans un nouveau répertoire 'gcov'. Ces fichiers sont les suivants

  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 qui a été 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


Détection des fuites de mémoire avec Cpputest

Cpputest vous permet de détecter automatiquement les fuites de mémoire en redéfinissant la famille de fonctions standard "malloc/free" pour utiliser ses propres enveloppes à la place. Cela lui permet d'attraper rapidement les fuites et de les signaler pour chaque exécution de test. Ceci est activé par défaut dans MakefileWorker.mk, donc c'est déjà fait avec les étapes décrites jusqu'ici.

To illustrate, let’s leak some memory in test_func() !

Going back to code.c, we add a malloc()  à la function, like so:

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

Maintenant, après avoir recompilé, 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

Cela montre quel test a causé la fuite, où la fuite s'est produite dans le code source, et ce qui se trouvait dans la mémoire qui a fui. Très utile !

Cette fonction présente quelques inconvénients :

  1. Cpputest utilise les macros du préprocesseur pour redéfinir dynamiquement tous les appels aux fonctions standard de gestion de la mémoire. Cela signifie qu'il ne fonctionnera que pour les appels dans le code source testé, puisque 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 qui est allouée pour toute la durée de vie du processus n'est pas destinée à être libérée. Cela peut provoquer beaucoup d'erreurs parasites si vous testez un module avec ce comportement. Pour désactiver la détection des fuites, vous pouvez faire ceci :

CPPUTEST_USE_MEM_LEAK_DETECTION=N


Vous souhaitez en savoir plus ?

Ce n'est que la partie émergée de l'iceberg lorsqu'il s'agit de toutes les fonctionnalités contenues dans cet outil. Outre les éléments de base abordés ici, il dispose également d'un framework de mocking, d'une couche d'intégration directe en C et d'un framework de plugins, pour ne citer que quelques exemples significatifs. Le dépôt contient également un répertoire entier de scripts d'aide qui peuvent aider à automatiser certaines des parties routinières du travail avec le framework.

J'espère que les informations présentées ici vous aideront à améliorer la qualité de votre code C/C++ grâce à cet outil formidable !

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> au right time.

By clicking "See Bird" you agree to Bird's Avis de confidentialité.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> au right time.

By clicking "See Bird" you agree to Bird's Avis de confidentialité.