Le principe des tests unitaires
Contrairement à un test d’homologation fonctionnelle qui consiste à valider le fonctionnement de l’applicatif dans sa globalité, l’objectif du test unitaire est de vérifier la validité d’une partie d’un traitement. Certaines méthodologies comme eXtreme Programming (XP) ou Agiles basent leur fondement sur l’élaboration de tests unitaires avant même le démarrage des développements de l’application. Quelle que soit la méthode de développement, il est impératif de réaliser des tests unitaires dans le but de vérifier le bon fonctionnement des programmes, faciliter le débogage et permettre de mettre en œuvre des tests de non régression. Afin d’éviter d’introduire du code de test unitaire au sein même du code de l’applicatif, la majorité des développeurs élabore des programmes de tests qui réalisent des appels à leur code afin de vérifier le fonctionnement. Pour faciliter la création de ces petits programmes, un framework dédié aux tests unitaires est apparu dans le monde Java par sous le nom de JUnit.
Présentation du framework JUnit
Il s’agit d’un logiciel open source, à l’initiative conjointe de Kent Beck (créateur de XP) et Erich Gamma (auteur d’ouvrages sur les Design Patterns), proposant un ensemble d’API et de classes sur lesquelles reposent les classes de test à développer. Il existe principalement trois composants :
- TestCase : permet de décrire un cas de test
- TestSuite : permet de décrire un scénario comprenant l’enchaînement d’un ensemble de cas de test ou même d’autres scénarii de tests
- TestRunner : permet l’exécution proprement dite des composants précédents
Afin de pouvoir mettre en place des cas de test, JUnit propose un ensemble de méthodes qui permettent de valider ou non l’exécution du programme testé dont voici les principales :
- « assertTrue » vérifie que le résultat retourné est un booléen de valeur « true »
- « assertNotNull » vérifie que l’objet passé en paramètre n’est pas nul
- « assertEquals » vérifie l’égalité entre deux types primitifs (string, int, double, etc.) passés en paramètre. Il existe plusieurs signatures de cette méthode en fonction des types de données à comparer et il est possible d’ajouter un delta pour la comparaison de numériques
- « assertSame » vérifie que les objets passés en paramètre sont identiques
- « fail » permet de faire échouer le test en affichant un message
Par ailleurs, il existe deux méthodes falcultatives à développer au sein du cas de test « setUP » et « tearDown » qui sont appelées respectivement au début et à la fin de l’exécution de la classe de test. Ceci permet d’initialiser un certain nombre de variables, d’objets ou de ressources (accès à une base de données, ouverture ou création de fichiers, etc.) et aussi de rétablir la situation à la fin d’un test (fermeture de la connexion à une base, fermeture ou suppression d’un fichier, etc.).
L’exécution des cas de tests par l’intermédiaire d’un scénario complet permet de disposer d’une analyse de l’ensemble des méthodes testées. Les résultats peuvent être fournis sous formes textuelles (sortie standard) ou en mode graphique selon le TestRunner utilisé.
JUnit par l’exemple
Voici un programme Java très simple contenant deux méthodes à tester unitairement :
1 2 3 4 5 6 7 8 9 10 11 |
package com.calculator; public class Transforme { public static double euroToFranc(double euro) { return euro*6.55957; } public static double francToEuro(double franc) { return franc/6.55957; } } |
Nous avons écrit une classe de test réalisant un appel de chaque méthode (en général, il est préférable de faire une classe de test pour une classe à tester). Voici le code source du cas de test :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.test; import com.calculator.Transforme; import junit.framework.TestCase; public class TestTransforme extends TestCase { double a = 15; public void testEuroToFranc() { double result = 6.55956*a; assertEquals(result, Transforme.euroToFranc(a), 0); } public void testFrancToEuro() { double result = a/6.55957; assertEquals(result, Transforme.francToEuro(a), 0); } } |
Notre scénario appelle un seul cas de test :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.test; import junit.framework.Test; import junit.framework.TestSuite; public class AllTests { public static Test suite() { TestSuite suite = new TestSuite("Test for com.test"); //$JUnit-BEGIN$ suite.addTestSuite(TestTransforme.class); //$JUnit-END$ return suite; } } |
Pour l’exécution de ce cas de test, il est possible d’écrire un programme s’appuyant sur la classe « TestRunner » mais la plupart des outils intègrent JUnit et un simple clic droit permet l’exécution des tests.
Voici un exemple de résultat graphique fourni par l’intermédiaire d’Eclipse pour lequel nous avons volontairement introduit une erreur pour la méthode « euroToFranc » au niveau du calcul :
JUnit à toutes les sauces
Le succès du JUnit dans le cadre des applications Java a permis de voir s’étendre le phénomène à la majeure partie des autres langages de programmation ou technologies dont voici une liste non exhaustive :
Par ailleurs, JUnit est intégré au sein de la plupart des IDE ou autres frameworks de travail. Ainsi, il existe des plug-in pour pouvoir mettre en place des classes de test JUnit et faciliter son utilisation (exécution et analyse) au sein de l’outil de développement Eclipse. De même, Maven ou Ant proposent des modules d’utilisation de JUnit.
Conclusion
Souvent considérée comme une perte de temps inutile au démarrage d’un projet, l’intégration de tests unitaires permet en général de gagner en efficacité sur la durée. Il ne faut évidemment pas tomber dans l’excès en écrivant une classe de test pour chaque méthode de l’applicatif, les fonctions triviales ne doivent pas être l’objet de tests unitaires. L’élaboration de tactiques de tests est nécessaire selon le contexte et la complexité de l’applicatif.
JUnit pour Java et l’ensemble des moutures pour les autres technologies sont particulièrement bien adaptés pour gérer la mise en place de tests unitaires. Le principe de fonctionnement et son utilisation sont assez simples, il n’y a aucune raison de passer à côté d’un tel outil. La majeure partie des équipes de développement s’appuyent sur cet utilitaire pour la confection de cas de tests techniques.
Jean-Sébastien MERCY
Références
1 http://www.junit.org
2 http://junit.sourceforge.net