Tests Unitaires en C++ avec CppUnit

Maintenant que j’ai terminé de coder ma librairie XTM, pour mon programme TuXtremSplit, je veux intégrer des tests unitaires !

C’est quoi des Tests Unitaires ??

Les Tests unitaires existent pour faciliter la vie des développeurs.
Le principe est d’écrire un jeu de tests pour chaque classe, puis d’utiliser un Continuous Integration server, comme Hudson ! (Voir comment Installer Hudson et executer des tests en C++) :-)
Ce dernier, selon sa configuration, va compiler votre code, puis exécuter les tests unitaires, pour enfin générer un rapport.

Concrètement, si quelqu’un commit un code qui provoque un bug, car il a mal appelé votre classe par exemple, Hudson râlera, et tout le monde en sera informé.
Du coup, vous pourrez découvrir rapidement des bugs, mais, à contre partie, les tests unitaires ralentissent le build.
D’où l’intérêt des Night builds si vous travaillez sur un gros projet, où le code sera compilé à minuit par exemple, pendant le temps où personne ne travaille (en principe :-P).

Comment faire des tests unitaires en C++

Pour se faire, il existe une série de Framework. Moi j’utilise CppUnit, car il existe un plugin pour Hudson.
Mais CppUnit est assez lourd, d’après les tests et forum que j’ai pu lire ainsi qu'avec mes premiers tests.

Pour commencer, il faut installer CppUnit.
Sous Ubuntu:

sudo apt-get install libcppunit-dev

Principe de fonctionnement

J’ai mis un peu de temps à le piger… donc je vous explique celui que j’ai compris, vous gagnerez du temps !
Je dis celui que j’ai compris, car il existe, si je dis pas de bêtises, 2 possibilités:

  • Soit vous définissez manuellement chaque tests à appeler dans chaque classe
  • Soit vous faites hériter d’une classe du framework votre classe de test, puis vous précisez les tests à effectuer, et le reste est automatique

Je vais vous expliquer cette dernière:

Prenons un exemple !

Je créer un projet appelé project, qui contient 2 classes : ClassA et ClassB.
La ClassA, contient une setVar(), qui prend un std::string en argument, et une getVar() qui retourne un std::string.
La ClassB, quand à elle, contient aussi une setValue() et une getValue(), mais elle travaille avec un long comme attribue, au lieu d’un std::string.

Je désire donc, écrire les tests unitaires de ces deux classes.

Il me faut créer une classe ClassATest et une ClassBTest ainsi qu’un main pour les tests.
Dernière chose, le Makefile aussi sera à modifier, pour compiler un programme test.

Commençons par le main() !

L’avantage de cette solution, c’est que le fichier .cpp qui contient le main(), ne change JAMAIS.
Du coup, vous n’avez besoin que de copier/coller ce fichier pour avoir le point de départ.
Le voici :

Maintenant, il ne reste plus qu’à créer une classe de test pour chaque classe de votre projet, et de lui faire hériter de CppUnit::TestFixture et d’utiliser les macros CPPUNIT_*.

Création de la classe de Test de ClassA

Le fichier header est assez simple:

En résumé, on utilise tout d’abord CPPUNIT_TEST_SUITE(), pour créer une nouvelle série de tests, et ont termine cette série par CPPUNIT_TEST_SUITE_END().
Puis, entre ces deux macros, il suffit d’utiliser la macro CPPUNIT_TEST() pour donner les noms des méthodes à appeler.

Maintenant, parlons du fichier .cpp :

Ici nous utilisons les macros CPPUNIT_ASSERT* pour définir nos tests.

Création de la classe de Test de ClassB

Ici rien de compliqué.
Le but est de vous prouvez que les tests unitaires seront exécuté sans avoir à déclarer les classes.

Voici le fichier header:

Puis le fichier .cpp:

Voilà, avec tout ceci, vous avez écrit vos tests.
Maintenant, il reste à faire le Makefile.

Makefile

Bon, pour le Makefile, il faut créer 2 parties :

  • La compilation du projet
  • La compilation des tests

Ont va utiliser des variables pour définir les fichiers du projet, et les fichiers de tests.

Voici son contenu :

Maintenant, un petit make, et vous devriez avoir un exécutable project, et un exécutable test.

Toutes les sources de l’article sont à cette adresse: https://github.com/zedtux/cppunit

Exécuter les tests !

Il ne reste plus qu’à générer les tests unitaires et voir si tout fonctionne bien ! :)

Il vous suffit donc, de lancer l’exécutable test.

En cas d’erreur, vous aurez se type de sortie :

./test
ClassATest::TestMain : assertion
ClassATest::TestMain2 : OK
ClassBTest::TestMain : OK
classatest.cpp:17:Assertion
Test name: ClassATest::TestMain
assertion failed
- Expression: myClassa.getVar() != testValue2

Failures !!!
Run: 3   Failure total: 1   Failures: 1   Errors: 0

Et quand tout fonctionne bien, vous aurez, ce type de sortie:

./test
ClassATest::TestMain : OK
ClassATest::TestMain2 : OK
ClassBTest::TestMain : OK
OK (3)

En plus de ca, un XML est généré.
Le but de ce fichier XML est qu’au final les tests seront exécuté dans Hudson, comme décrit au début.
Le plugin de Hudson ne fait que parser le fichier XML, pour générer le rapport.

Je vais écrire un autre article pour la configuration de Hudson.