Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS JAVA FAQs TUTORIELS JAVASEARCH SOURCES LIVRES OUTILS, EDI & API ECLIPSE NETBEANS BLOG DISCUSSIONS TV
Trucs et astuces
Validité des paramètres
Documenter votre code
Assertions
Tests unitaires
Design patterns - GOF
Adaptateur
Composite
Décorateur
etat
Façade
Kit
Monteur
Pont
Proxy
Singleton
Design patterns - Avalon
IOC - Inversion Of Control
SOC - Seperation Of Concerns
COP - Component Orientated Programming
SOP - Service Orientated Programming
Autres articles
Cahier
XML Sax en java
Fractal
AspectJ

Voir aussi
Patterns du GRASP
héritage avec des EJB Entiy CMP

Ressouces java
Informations
Cours
Livres
FAQ
Outils
EDI
Ressources uml
Cours
Livres
Forums d'entraide
Géneral Java
J2EE
JBuilder
Outils EDI
Méthodes/UML/Mérise


Tests unitaires
15 Janvier 2003 modifié le 4 Octobre 2003
Version 1.1
Par Sébastien MERIC
Remerciements : Johann Heymes, Stefan Bertholon, Laurent PETIT

L'utilisation de framework de tests unitaires est essentielle à la constitution d'un code robuste. Elle s'inscrit dans la lignée des articles précédents et viens en complément, vous aider, d'une part à placer votre code en situation difficile, d'autre part, elle en améliore la lisibilité ! Est-ce possible, alors qu'on écrit plus de code que necessaire ? N'allez-vous pas passer encore du temps à écrire du code en plus de ce qui est demandé, déjà qu'il y a les commentaires à écrire. Nous verrons que loin de ralentir le développement, il vous fait gagner un temps de débuggage franchement important. Sachant qu'avant d'avoir mis au point ces diverses techniques, le débuggage était considéré comme occupant une part allant de 50% du temps pour un expert à 90% pour un débutant, je vous laisse imaginer le gain en performances que vous allez faire.

Introduction

Comme pour chaque article de cette série, les exemples sont en java mais le concept vaut pour tous les langages (au moins objets). Dans cet article particulier, je vous décris l'utilisation d'un framework particulier : JUnit. Les concepts restent toutefois les mêmes pour d'autres frameworks, et les bonnes et mauvaises pratiques du chapitre utilisation s'appliquent probablement aussi la plupart du temps. Par ailleurs,pour vous permettre de travailler, je vous donne les urls de téléchargement du framework, pour java, c++, delphi et .Net. Pour avoir déjà utilisé le framework de Delphi, je peux vous dire qu'il n'est pas rigoureusement équivalent à celui de java, mais que l'adaptation du code java en pascal est vraiment simple. Les voici donc :

javaJUnit
c++CUnit
DelphiDUnit
.NetNUnit

Avant propos

Le terme framework est régulièrement utilisé dans ce document, je me permet donc d'en proposer une traduction/définition :
Un framework est une infrastructure logicielle qui facilite la conception des applications par l'utilisation de bibliothèques de classes ou de générateurs de programmes, soit dit en quelques mots : un cadre de développement.

Concepts

Quand ?

Quand doit-on écrire des tests, c'est à dire à quelle phase du développement doit-on s'y mettre ? Sur ce point, l'une des pratiques les plus en vogue aujourd'hui, l'eXtreme Programming, nous dit de les écrire avant même de commencer à coder. Certains pratiquent aussi le pair programming (programmation en binôme) de la manière suivante : discussion du binôme autour de développement à effectuer (on ne parle pas des 5 jours à venir mais des 30 minutes à venir) puis chacun retourne à sa machine et l'un des développeurs écrit les tests, tandis que l'autre implémente. Quoi qu'il en soit, les tests s'écrivent en même temps que le code. Légèrement avant, légèrement après ou exactement en même temps ? Peu importe votre méthode, mais vous ne devez pas écrire tous les tests d'un coup avant le développement, ni les écrire après avoir terminé l'implémentation (ce serait totalement inutile). Pour ma part, j'opte plutôt pour l'écriture des tests avant le code ; en effet, cette pratique a plusieurs avantages :

  • Affiner l'analyse, en particulier, si en écrivant le javadoc avant le code, vous avez recensé les besoins, ici vous recensez les cas d'utilisations,
  • Apporter l'ensemble des règles pré et postconditions traitées dans l'article sur les assertions,
  • Apporter aussi l'ensemble des traitements de vérification de paramètres également traités dans un article précédent,
  • Eviter d'écrire du code inutile. En effet, si vos tests (bien écris s'entend) passent tous, inutile d'ajouter du code à votre classe. Donc vous implémentez jusqu'à ce que tous les tests passent, puis vous arrêtez et passez au point suivant.

Documenter !

Ne vous méprenez pas sur ce titre, il ne s'agit pas de documenter le test, mais de s'en servir comme d'une documentation technique. En effet, l'utilisation de vos classes est toujours difficile à documenter. Qui plus est, une documentation à part risque fort d'être rapidement désuète. Ceci vous décourage dans l'écriture d'une documentation et vous mène souvent à la rédaction de celle-ci en fin de projet, soit beaucoup trop tard. Le test est en lui-même un exemple d'utilisation et de manipulation de vos classes, il vous permet donc de documenter celles-ci. Donc, en plus d'ajouter de la valeur logicielle, le test apporte de la valeur documentaire à vos codes, vous hésitez encore à en écrire ?


Une simulation d'IHM développée en quelques minutes

Si vous partez d'un code qui fonctionne, que vous lui ajoutez un peu de fonctionnel, où se trouve le bug ? Dans les lignes que vous avez ajouté bien entendu ! Donc plus vous testez régulièrement, plus le bug est facilement détectable. Et oui, dénicher un bug dans 2 ou 3 lignes de code regroupées est beaucoup plus simple que de le detecter dans une trentaine de lignes réparties sur plusieurs classes ! Alors le problème c'est qu'aujourd'hui, vous êtes obligé d'écrire, le code métier, le code de persistance et le code de l'IHM avant de pouvoir vraiment tester quoi que ce soit. Le framework de test vous apporte donc une solution sur mesure pour résoudre ce problème : il faut quelques lignes seulement pour écrire le client graphique (même s'il ne se présente pas franchement comme l'IHM définitive). Vous vous affranchissez donc de deux problèmes en une seule fois : ne pas être obligé de coder l'interface graphique tant que le métier n'est pas implémenté, et ne pas dépendre du débugage de l'IHM elle-même pendant le codage du métier. Toujours des hésitations ?


Quelques secondes pour mettre toute votre application à l'épreuve

Une fois les tests écrits, il suffit de quelques secondes pour les lancer, et obtenir un compte-rendu. Et vous obtenez en quelques secondes, ou quelques minutes si votre application est vraiment importante et que vous lancez vraiment absolument tous les tests, un compte-rendu exhaustif de ce qui marche et de ce qui ne marche pas ! Avez-vous déjà pris le temps de tester une application de manière exhaustive ? C'est pourtant ce qu'il faut faire avant de la livrer, et ce qu'il faudrait faire à chaque modification. Autant dire que même pour une application de petite taille, si vous la testez manuellement, ça vous prendra au bas mot plusieurs dizaines de minutes ! Vous ne le faites donc que rarement et ça vous est préjudiciable, vous allez encore passer des heures à débugger un petit rien simplement parce qu'au moment où vous avez généré le bug, vous n'avez pas tout testé ! Si vous n'êtes toujours pas convaincu par le fait qu'il faut écrire des tests, j'ai encore un paragraphe pour vous faire changer d'avis, mais vous êtes un peu dur ;-)


Non régression

Combien de fois ai-je entendu "Je ne comprends pas pourquoi, hier ça marchait ! " ? Impossible de repondre... Trop souvent en tout cas. Le pire, etant certainement de l'avoir entendu sortir de sa propre bouche... Vous ne voulez plus vous entendre dire à votre responsable que hier ça marchait, ou vous ne voulez plus entendre vos collègues vous dire ce genre de lieu commun des développeurs. De plus, afin que la maintenance de votre code puisse être éffectuée par tous, sans pour autant que de nouveaux bugs soient introduits par un tiers, il faut lever un drapeau "attention, ne marche pas comme prévu ! ". Les tests unitaires sont là pour ça. La non régression du code, c'est donc, la certitude que l'on avance. C'est aussi la confiance qui s'installe : "je peux modifier mon code puisqu'il résiste au pire, je saurais immédiatement s'il y a bug, où et pourquoi". Et la confiance en soi donne la force d'avancer plus vite. Alors, perdre son temps à écrire quelques tests, c'est gagner des heures de débug (ce qui vous intéresse le moins dans votre métier j'en suis certain), ça n'est donc pas utile, mais essentiel. Dans quelques semaines, après avoir acquis suffisamment de réflexes pour l'écriture de ces tests, vous repenserez à cette période ancestrale durant laquelle vous écriviez du code sans écrire de tests, et vous vous demanderez alors comment vous faisiez.

Utilisation

Ecrire une série de tests pour une classe

Commençons par observer la syntaxe à respecter pour écrire un test, ensuite, nous verrons comment réunir plusieurs séries de tests pour un lancement simultané. Enfin, une fois que l'on sera capable d'écrire les tests, on essayera de débrouiller les situations pour lesquelles écrire les tests et dans quelles conditions les écrire. Une classe héritant de junit.framework.TestCase pour commencer, c'est le socle de votre série. Dans cette classe, vous pouvez définir les séries de tests tout simplement en déclarant des méthodes publiques dont le nom commence par les quatres lettres (en minuscules) test. Voilà, vous avez défini un testCase que vous pouvez passer en paramètre à un TestRunner pour lancer vos tests. C'est simple non ? Bien entendu, il reste encore à comprendre ce qu'il faut mettre dans les méthodes testXXX(). Vous n'allez pas être dépaysé si vous avez déjà lu l'article sur les assertions, car les tests se font justement à coup d'assertions. Voilà un exemple de test, je mets une méthode main() dans la classe afin de la rendre exécutable, il n'y a rien d'obligatoire à ça bien entendu.

 1  package com.developpez.tutoriels.astuces.tests;
2 3 /** 4 * classe de test pour une pseudo classe de traitement d'une facture 5 */ 6 7 public class TestFacture extends junit.framework.TestCase {
8 public void main(String[] args) {
9 // pas de vérification des paramètres, ce n'est pas l'objet 10 junit.textui.TestRunner.run(TestFacture.class);
11 }
12 13 public TestFacture(String name) {
14 // ce constructeur est obligatoire car il n'existe pas 15 // de constructeur par defaut dans les TestCase 16 super(name);
17 }
18 19 public void testAjoutArticles() {
20 Facture maFacture = new Facture();
21 maFacture.add(new Article("article 1", 3, 150.0));
22 maFacture.add(new Article("article 2", 1, 50.0));
23 24 assertNotNull("La facture ne devrait pas être null", maFacture);
25 assertEquals("Le total de la facture est mal calculé",
26 3 * 150 + 50, 0.0001,
27 maFacture.getTotal());
28 assertEquals("Le nombre d'articles est mal calculé",
29 4,
30 maFacture.countArticles());
31 }
32 public void testValidationFacture() {
33 Facture maFacture = new Facture();
34 maFacture.add(new Article("article 1", 3, 150.0));
35 maFacture.add(new Article("article 2", 1, 50.0));
36 maFacture.valide();
37 38 assertTrue("la validation de la facture n'a pas eu lieu",
39 maFacture.isValide());
40 41 try {
42 maFacture.add(new Article("article 3", 1, 20.20));
43 fail("facture modifiée après validation");
44 } catch (FactureException e) {
45 // interdit de modifier une facture valide donc 46 // c'est normal d'être ici 47
48 }
49 }
50 }

A remarquer tout de suite dans ce code :

  • J'utilise le testRunner en mode texte, je le trouve plus pratique car il envoie tout sur la sortie standard ou erreur et en général votre IDE permet de vous diriger directement vers le code incriminé en cas d'erreur. Il existe toutefois un testRunner AWT et un SWING qui présente les résultats de manière plus graphique. A vous de choisir.
  • Je vous indique quelques méthodes assertXXX, il en existe beaucoup, leur nom est suffisamment parlant pour que je ne vous fasse pas un glossaire exhaustif.
  • L'assertion sur l'égalité de réels se fait à l'aide de trois réels, les deux réels à comparer, et une approximation.
  • Les assertions d'égalités vous livrent comme message "value" expected but "value" found.
  • Un message (String) peut être spécifié pour toutes les assertions, il sera livré si elles ne sont pas vérifiées.
  • Enfin, le nom des méthodes testXXXX sera repris dans les messages, veillez à ce qu'il signifie quelque chose.

Réunir les tests en une suite de tests

Comme lancer un test pour une classe est loin de tester l'application complète, il est possible de réunir les tests dans des faisceaux de tests, les TestSuite. Ensuite, leur lancement se fait de la même manière qu'avec un TestCase, . Bien entendu, vous pouvez réunir plusieurs TestSuite dans un TestSuite plus important, etc. jusqu'à obtenir un test de l'application complète. Ceci est avantageux sur une application pour laquelle les tests peuvent prendre plusieurs minutes, en effet, l'intéret des tests est de pouvoir les lancer presque toutes les 5 minutes afin de détecter très tôt les bugs.


Bonnes pratiques

Une classe, un test

Une classe devant être testée, autant qu'elle dispose de son propre module de tests. Comme la notion de module de granularité la plus fine en java est la classe, on écrit une classe de test pour chaque classe à tester.  Cela vous permet de retrouver facilement le test attaché à une classe ; pour ce faire, il suffit de prendre la convention de nommage : TestMaClass et le tour est joué.

Tests dans le même package que la classe à tester

Cette résolution est simple à expliquer. Si vous voulez pouvoir tester l'intérieur d'un composant (classe déclarée de visibilité niveau package ou méthodes déclarées de visibilité niveau package), il vous faut écrire la classe de tests dans le même  package. Par ailleurs, pour retrouver la classe de tests spécifique d'une classe, il est plus facile de la chercher dans le même package. Enfin, si deux classes portent le même nom mais se trouvent dans des packages différents, autant que les classes de tests se trouvent aussi dans des packages différents.


Scénarii

Ecrire les scénarii courants

Vous avez maintenant écrit les assertions pour vous assurer de la véracité de ce que vous supposiez avoir écrit. A présent, il vous reste à écrire les scénarii d'utilisation courante de votre classe. En effet, c'est déjà important que ce qui est attendu fonctionne, mais les méthodes s'appellent généralement suivant des enchaînements logiques, qui doivent aboutir à une réalisation donnée,  autant s'en assurer.

Et surtout mettre l'application en difficulté

Ecrire les scénarii catastrophe pour vérifier que l'application résiste bien. Que l'utilisateur à une bonne chance de recevoir un message clair dans ce type de cas. Bref, faire faire par vos TestCase le test du client qui s'assied sur le clavier.

Mauvaises pratiques

Ne pas mettre les classes dans le même répertoire que les tests

En effet, évitez de mettre ensemble les sources des tests avec celles de l'application car il est plutôt probable que vous n'aillez pas envie, en particulier parce que ce n'est pas judicieux, de livrer et déployer votre application avec les tests. Pour pouvoir gérer aisément la fabrication des jars et la compilation, il est donc préférable de séparer les sources.

Ne  pas écrire de tests triviaux

Ecrire des tests triviaux revient à perdre du temps : c'est inutile. Ca donne l'impression que l'écriture de tests fait perdre du temps : c'est fallacieux. Malheureusement il est plus facile de dire n'écrivez pas de tests triviaux que de déterminer la trivialité ou non d'un test. Comme précisé dans la rubrique précédente, pour être certain d'avoir écrit des tests, si possible non triviaux, le mieux est de commencer par écrire les scénarii attendus, et les scénarii catastrophe. Vous obtenez déjà un bon point de départ. Ensuite, ajoutez tous les invariants de classe si vous n'utilisez pas déjà un framework de programmation par contrat comme expliqué dans l'article sur l'utilisation des assertions

Pas d'effet de bord dans les tests

Vos tests doivent pouvoir tourner 100, 200, 1000 fois et plus encore... Si l'un de vos tests provoque des effets de bord sur l'état de votre système, vous aurez alors des tests qui ne se vérifient qu'une seule et unique fois. Vous devez donc absolument éviter les effets de bord dans vos tests.

Ne présumer pas de l'ordre dans lequel les tests sont lancés

En particulier, ne présumez pas de l'ordre dans lequel sera lancé vos tests. En effet, le framework utilise l'introspection (java.lang.reflect) mais vous n'avez aucune idée de la manière dont la jvm implémente cette introspection, est-ce qu'elle prend les méthodes dans l'ordre d'implémentation ? Dans le sens inverse à cause d'une pile (j'empile, je désempile) ? ou par ordre alphabétique (pourquoi pas ?) ?etc... Vous ne pouvez donc pas écrire des tests du type testInsert testModif testSupprime ou test insert insert l'enregistrement que testModif modifie et que testSupprime supprime. Il faut donc tester un scénario complet, de la création à la suppression en passant par la modification.

Ne laisser pas, surtout dans les couches de persistances, d'effets de bords

Pensez aussi à laisser le système précisément dans l'état dans lequel il se trouvait avant que les tests ne soient lancés, surtout pour toute la partie persistante. En effet, imaginez que vous écriviez un test d'insertion de société dans votre système, mais que vous ne pensiez pas à supprimer cette société de la base de données sur laquelle votre système repose. Que se passera-t-il au deuxième lancement de vos tests, sachant que l'une des contraintes sur les sociétés, est l'unicité de raison sociale ? Et oui, le test d'insertion échoue alors que tout se passe à merveille puisque même la contrainte est traitée. Donc le test ne fonctionne qu'une fois, autant dire que c'est inutile !

Conclusion

En résumé, écrire des tests permet

  • une analyse de petite granularité
  • la certitude d'engendrer peu de bugs
  • la non régréssion du code
  • la documentation efficace de votre code

L'écriture des tests n'est pas difficile et n'est pas longue non plus (à condition d'être pratiquée au fur et a mesure du développement). Pour comble, apprendre à coder son premier test ne prend que 10 minutes. Et même si pour apprendre à écrire efficacement ses tests, il faut de la pratique, en écrire "peu efficacement" reste plus efficace que de ne pas en écrire du tout. J'ai beau essayer d'explorer ce qui pourrait nous pousser à ne pas en écrire, je ne trouve rien de probant. Il ne reste donc qu'une conclusion possible : dès demain... tut tut tut... non dès tout de suite un clic sur le lien qui correspond à votre environnement de travail pour un download immédiat.

 

 

Modifications entre la version 1.0 et 1.1 : corrections de bugs dans le code exemple en java.
  • Ligne 25 et 28 l'assertEquals() s'écrit assertEquals(Message en cas d'échec, valeur attendue, valeur trouvée)
  • Ligne 38 assertXXX s'écrit toujours avec le message comme premier paramètre si un message est fourni.
  • Ligne 48 déplacée en 43 et suppression du return pour permettre de lancer d'autres tests à la suite de celui-ci.
Copyright (c) 2003 Sébastien MERIC. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being Tests unitaires, and Back-Cover Texts being Ce document à été écrit pour la communauté de développeurs francophones "www.developpez.com". A copy of the license is included in the section entitled "GNU Free Documentation License".
/java/astuces/tests  

Responsables bénévoles de la rubrique Java : Eric Siber et Baptiste Wicht - Contacter par EMail :
Vos questions techniques : forum d'entraide Java - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.