JUnit
Le
framework Java open source pour la réalisation de tests
unitaires
Jérôme Cheynet
Dossier réalisé en 3ème
année à l'école Ingénieurs 2000, filière
informatique et réseau, Université de Marne-la-vallée.
1 Résumé
Et si vous arrêtiez de laisser des bugs derrière vous
? Les tests unitaires sont là pour tester chacunes de vos
méthodes : Une méthode fait-elle bien ce que vous
voulez qu'elle fasse ? Le fait-elle encore après que vous
l'ayez modifié de fond en comble ?
Si vous programmez en Java, héritez simplement des classes
de JUnit conçues pour faciliter la création des tests
unitaires, et lancez vos tests avec une des interfaces utilisateur
proposées.
Consacrez le temps que vous passez consciemment ou inconsciemment
pour mettre en place vos propres tests à la création de
tests JUnit.
2 Introduction
2.1 Le génie logiciel ou l'atteinte des objectifs que l'on
s'est fixé
La réalisation de programmes peut être une tâche
complexe, et il n'est pas toujours évident de parvenir
rapidement au but que l'on s'est fixé. Il existe
heureusement des méthodes et des outils qui permettent de ne
pas s'éloigner du but. La conception d'un cahier des charges
fonctionnelles, avec la définition de "Besoins
fontionnels" - c'est à dire la définition des
fonctionnalités que l'utilisateur attend du programme –
est une des premières étapes du processus de
développement. Tout au long du processus de développement,
il faut veiller à une chose : parvenir à finaliser le
programme, en répondant au cahier des charges fixé par
le client.
JUnit fait partie de ces outils qui aident le programmeur dans ce
sens. Il est tout d'abord bon de savoir que JUnit n'est pas une usine
à gaze qui permet d'automatiser les tests de fonctionnalités
macroscopiques (celles fixées par le client). JUnit est
quelque chose de très simple, destiné aux personnes qui
programment. JUnit est tellement petit et tellement simple que la
seule comparaison possible en terme de rapport complexité
d'utilisation / utilité est le bon vieux "println"
ou les "assertions". Mais JUnit permet d'aller plus loin...
Ainsi, comme le dit l'un des gourous de l'Extrem Programming : "Never
in the field of software development was so much owed by so many to
so few lines of code" (Martin Fowler).
2.2 Le développement... là où le framework JUnit
peut vous aider à atteindre vos objectifs
Pourquoi JUnit peut-t-il à ce point révolutionner
l'efficacité avec laquelle vous programmez ?
Un programmeur qui vient de programmer un petit morceau de
programme buggué va s'aider d'un debuggeur ou de la sortie
texte sur la console pour savoir ce que fait son petit morceau de
code. Un programmeur qui utilise JUnit va avant d'écrire le
petit morceau de code buggé d'abord se demander ce que le
petit morceau de programme doit faire, puis il va écrire un
petit bout de code capable de vérifier si la tache a bien été
réalisée, à l'aide du framework JUnit (le test
unitaire). Enfin il va réaliser le petit morceau de code qu'il
devait faire, et il va constater que son code est buggé parce
que le test unitaire ne passe pas. Le
test unitaire constate en effet que la tache n'est pas
complétée. Le programmeur qui utilise JUnit va alors
débugger son code jusqu'à ce que la tache soit
correctement réalisée (Rq: Il va éventuellement
utiliser un debugger ou des affichage sur la sortie texte pour y
parvenir).
2.3 Pourquoi faire tout ça ? (pourquoi travailler plus, je
n'ai pas le temps !)
Parce que pour une raison ou pour une autre, vous devez
programmer efficacement. Vous ne pouvez pas vous permettre de sortir
des sentiers battus en programmant "hors sujet" ou en
oubliant une fonctionnalité. Votre seul objectif en
programment est alors de passer les tests unitaires. "The point
of TDD is to drive out the functionality the software actually
needs, rather than what the programmer thinks it probably ought to
have", Dan North.
Toujours dans un soucis d'efficacité, vous ne pouvez
pas passer non plus votre temps à chercher un bug (ou vous
n'aimez pas faire ça...). Que se serait-il passé si le
programmeur n'avait pas constaté le bug ? Si vous développez
une test unitaire, vous vous ajoutez une marge de sécurité.
Vous laissez moins de bugs derrière vous.
Parce que vous voulez savoir si vous n'êtes pas en
train de faire régresser votre code. JUnit vous dira tout de
suite si vous avez perdu une fonctionnalité en cours de
route, par exemple après avoir supprimé 200 lignes de
code en pensant bien faire. Ant peut faire appel à JUnit :
vous saurez à chaque compilation s'il y a eu régression.
Le refactoring devient moins difficile et aléatoire : vous
gagnerez là encore en efficacité. A noter que JUnit
est conçu à la base pour éviter ce genre de
problème...
Parce que vous travaillez à plusieurs sur un projet.
Vous avez (ou en tout cas vous aurez) une plus grande confiance dans
un bout de code fourni avec son test unitaire que dans un bout de
code fourni sans. Si voir la barre verte (signe d'un test passé
avec succès) n'est pas suffisant pour gagner votre confiance,
vous avez alors une seconde possibilité de vérifier le
bon fonctionnement, qui est plus rapide que la lecture du code
source : la lecture du test. Si le test vous inspire confiance et
qu'il passe, alors pourquoi perdre du temps en lisant (pour ne pas
dire décrypter !) le code source ? Vous pouvez enfin faire
confiance au code des autres, et vous concentrer sur la résolution
de vos bugs à vous ! Là encore, vous pouvez gagner en
terme d'efficacité.
Réaliser des tests unitaires avec JUnit n'est pas seulement
une alternative aux "println", c'est plus que ça.
C'est programmer en ayant une plus grande chance de satisfaire un
objectif, fut-il mineur, dans un but d'amélioration générale
de la qualité du logiciel.
2.4 Efficacité, efficacité... Y'a t il un rapport avec
l'Extreme Programming ?
Oui et non. En adoptant JUnit,
vous adoptez deux des precepts de l'Extreme Programming. En effet, la
réalisation de tests unitaires est plus que précaunisée
par les gourous de l'Extrem Programming, que sont (à
l'origine) Kent Beck, Ken Auer, Ward Cunningham, Martin Fowler
(cf.
http://www.xprogramming.com/what_is_xp.htm).
"Top XP teams practice
"test-driven development", working in very short cycles of
adding a test, then making it work", Ronald E Jeffries (un
autre gourou de l'XP).
"So XP uses a process of
continuous design improvement called Refactoring, from the title of
Martin Fowler's book, "Refactoring: Improving the Design of
Existing Code", Ronald E Jeffries (encore lui)
Vous devez aussi savoir que vous
utilisez un outil écrit par Erich Gamma and Kent Beck. Or Kent
Beck est l'initiateur de l'Extreme Programming...
L'Extreme Programming est une méthode
en 12 commandements, le fait de créer un test puis de passer
le test, et le fait de faire un maximum de "refactoring" ne
sont que 2 de ces 12 precepts. Savoir utiliser JUnit ne fait donc pas
d'un programmeur non XP un programmeur XP, d'autant moins que JUnit
peut être utilisé que
pour le refactoring : ceux qui programment proprement
n'ont pas attendu XP pour travailler ainsi. Vous n'êtes donc
pas obligé d'utiliser les méthodes XP pour tirer partie
des tests unitaires. Il est bon d'aller ailleurs que sur les pages
web des concepteurs de JUnit et de XP, pour comprendre que les tests
unitaires ne sont pas forcément liés à XP. La
page Advanced Unit Testing [X] est une source d'information sur les
tests unitaires qui fait un peu mieux la part des choses entre les
tests unitaires et XP.
Si en plus du refactoring vous
voulez utiliser JUnit pour faire du Test Driver Developpement, vous
feriez bien de regarder les autres points de cette méthode :
http://www.xprogramming.com/xpmag/whatisxp.htm.
En utilisant toutes les possibilités d'un travail avec JUnit,
vous appliquez un point majeur d'XP (les 10 autres points d'XP ne
sont pas des moindres cependant, par exemple le fait de travailler
par paire sur une seule machine). Autrement, nous ne faites
qu'améliorer la qualité de votre travail en utilisant
les tests unitaires.
3 L'écriture d'un test unitaire simple pour une classe simple
Le net foisonne d'exemples de tests unitaires. Lorsque l'on ne
connait pas JUnit, on trouve assez rapidement le "célèbre"
JUnit Cookbook, écrit par ceux qui ont écrit JUnit :
http://junit.sourceforge.net/doc/cookbook/cookbook.htm.
Contrairement à ce que l'on pourrait s'attendre, le Cookbook
n'est pas un bon point de départ quand on ne connait pas
JUnit, car toutes les techniques pour créer un test sont
abordées, alors qu'en fait la plupart des gens utilisent une
seule de ces techniques. Après avoir lu ce qui suit, ou tout
autre exemple simple, vous pourrez aprofondir vos connaissance sur
JUnit avec ce CookBook.
Dans ce dossier nous nous
adressons aux informaticiens qui ne connaissant pas les tests
unitaires. Nous allons donc montrer ce qu'est un test unitaire
sur un exemple qui se veut le plus simple possible, pour ne pas que
vous passiez la moitié de votre temps à comprendre ce
qu'est censé faire le code : il s'agit d'une calculatrice à
4 opérations. Nous allons aborder la méthode de
fabrication et d'invocation d'un test qui est la plus souvent
utilisée (en tout cas quand on utilise l'IDE Eclipse
www.eclipse.org
, ou bien lorsqu'on débute). Parallèlement nous verrons
comment créer le test sans l'aide d'une IDE, pour mieux
comprendre ce qu'est le framework JUnit. Dans la section suivant,
nous verrons comment utiliser les tests unitaires quand le projet
devient plus gros : avec Ant.
3.1 Le code à tester
Voici tout d'abord le code à
tester. Attention, nous ne faisons pas de Test Driven
Developpement (ou Test First) comme avec la méthode XP : ici
le code existe d'abord, puis nous le testons.
/*
*
Created on 28 oct. 2003 at 19:23:36 by Jerome
*
*/
package
fr.umlv.exposeJUnit.calculators;
/**
*
@author Jerome
*
*/
public
class FourOpCalculator
{
public
int add(int
a, int
b)
{
return
a + b;
}
public
int sub(int
a, int
b)
{
return
a - b;
}
public
int mul(int
a, int
b)
{
return
a * b;
}
public
int div(int
a, int
b)
{
return
a / b;
}
}
3.2 Génération ou création de la classe du test
unitaire
La technique pour créer un test consiste à créer
une nouvelle classe, portant pour des raisons de commodité le
nom de la classe que vous voulez tester, suivi de "Test".
Cette nouvelle classe doit hériter de la classe "TestCase".
public
class FourOpCalculatorTest
extends TestCase
{}
Avec Eclipse, vous ne pourrez hériter de TestCase qu'en
ayant ajouté le jar de JUnit à votre build path (Illustration 1).
 Illustration
1 Le .jar de JUnit est déjà fourni avec Eclipse
Vous bénéficiez d'un Wizard pour créer le
test unitaire (cliquez au préalable sur la classe à
tester).
Etape 1 :
Choix d'un type de test. Dans un premier temps, on ne crée
que des TestCase. Les TestSuite permettent seulement de rassembler
des TestCase et d'autres TestSuite.
|

|
Etape 2 :
Pensez à séparer les sources des tests des
sources normale, afin de faciliter la création d'un .jar
pour votre client qui ne contienne pas les tests unitaires
|

|
Etape 3, facultative :
Les racines des méthodes à tester peuvent être
auto-générées
|

|
Si vous n'utilisez pas Eclipse, téléchargez le .zip
de JUnit et ajoutez le jar au classpath, car vous allez devoir
compiler la classe de test.
3.3 Ecriture du test unitaire
Nous réalisons maintenant un "test de bon
fonctionnement" d'une des méthodes de la classe testée
(c'est à dire le test unitaire). Toujours pour des raisons de
commodité, il est bon de reprendre le nom de la méthode
testée. Cependant il arrive quelques fois (il est recommandé
que ça arrive quelques fois) qu'une méthode ait
plusieurs tests qui lui soient associés, dans ce cas, vous
devrez trouver des noms permettant de comprendre tout de suite d'où
vient le problème (si le test échoue, c'est ce nom qui
vous sera présenté, plus éventuellement des
informations supplémentaires comme nous allons le voir).
Points très importants :
La méthode doit avoir un nom débutant par
"test".
Elle doit être déclarée public, et ne
rien renvoyer (void).
Eclipse permet de générer automatiquement ces
méthodes (cf, l'étape 3 du Wizard).
Voici un exemple d'une telle méthode :
public
void testSub()
{
int
result = a
- b;
assertEquals(result,
simpleCalculator.sub(a, b));
}
3.3.1 Message d'erreurs générés quand le test ne
passe pas
Ce test "passe" si les deux arguments de assertEquals
sont égaux . Généralement on met la valeur de
référence attendue par le test en premier argument, et
en second argument, la valeur obtenue auprès de la méthode
testée. En cas d'erreur, cela permet d'avoir un message
d'erreur qui a un sens (expected ... but was ...) :
junit.framework.AssertionFailedError:
expected:<8> but was:<7>
at
junit.framework.Assert.fail(Assert.java:47)
at
junit.framework.Assert.failNotEquals(Assert.java:282)
at
junit.framework.Assert.assertEquals(Assert.java:64)
at
junit.framework.Assert.assertEquals(Assert.java:201)
at
junit.framework.Assert.assertEquals(Assert.java:207)
at
fr.umlv.exposeJUnit.calculators.FourOpCalculatorTest.testAdd(FourOpCalculatorTest.java:45)
Remarque : Si nous n'avions pas fait d'appel à la
méthode assert, le test passerait à tous les coups.
JUnit ne vous oblige pas à faire un test qui passe par une de
ces méthodes de validation, il ne vous oblige aucunement à
écrire un test significatif. L'existence d'un test unitaire
n'apporte à priori pas de garantie particulière sur la
qualité du code testé (cf 7.1, Réaliser
un bon test).
4 Lancement du test unitaire
Comment savoir si le test qu'on vient d'écrire passe ?
Il faut lancer le test. Celui-ci ne se lance pas comme un
programme java normal. Avec Eclipse, on a l'impression d'éxécuter
le test comme s'il comportait une méthode main. Par contre en
ligne de commande, c'est différent.
4.1 Eclipse
Avec l'IDE Eclipse, la vue graphique s'obtient facilement : Il
suffit de lancer le TestCase en temps que "JUnit Test" (Run
As... JUnit Test). Par défaut, vous verrez une barre rouge en
cas de problème, et seulement un message dans la barre d'état
en cas de réussite. Il est possible de voir aussi la barre
verte en décochant une option (Illustration 2)
 Illustration
2 Décochez l'option pour que la "green bar"
s'affiche
4.2 En
ligne de commande
Sans l'aide d'Eclipse, vous devez compiler le test. Le test ne
s'exécute pas tel quel : il n'y a pas de méthode main.
C:\workspace_junit\calculatrice_pasAnt\bin>java fr\umlv\exposeJUnit\calculators\FourOpCalculatorTest
Exception in thread "main" java.lang.NoClassDefFoundError: fr\umlv\exposeJUnit\calculators\FourOpCalculatorTest (wrong name: fr/umlv/exposeJUnit/calculators/FourOpCalculatorTest)
Il faut lancer en fait un "TestRunner", qui se trouve dans
le .jar de JUnit. Il existe plusieurs TestRunner, un pour chaque type
d'interface. Vous avez le choix entre une interface en mode texte,
deux en mode graphique (awt ou swing). En paramètre du
TestRunner, vous fournissez le nom de la classe de test.
4.2.1 Interface d'affichage texte (texteui) :
C:\workspace_junit\calculatrice_pasAnt\bin> java -cp .;c:\junit\junit3.8.1\junit.jar junit.textui.TestRunner f
r.umlv.exposeJUnit.calculators.FourOpCalculatorTest
.F...
Time: 0,02
There was 1 failure:
1) testAdd(fr.umlv.exposeJUnit.calculators.FourOpCalculatorTest)junit.framework.
AssertionFailedError: add marche mal expected:<7> but was:<8>
at fr.umlv.exposeJUnit.calculators.FourOpCalculatorTest.testAdd(FourOpCa
lculatorTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
FAILURES!!!
Tests run: 4, Failures: 1, Errors: 0
4.2.2 Interface d'affichage graphique (swingui) :
Le TestRunner en swing permet de charger des TestCase et des
TestSuite. Il affiche sous la forme d'un arbre le résultat du
test unitaire (Illustration 3)
 Illustration
3 TestRunner "swingui"
5 Ecrire un test plus complexe
Nous avons vu comment écrire un test simple pour une classe
très simple. Le framework JUnit met à disposition
d'autres outils qui permettent de faciliter l'écriture des
tests unitaires.
5.1 La
classe Assert
Le propre d'un test unitaire est d'échouer quand le code
testé ne fait pas ce qui est prévu. Pour faire échouer
un test JUnit (c'est à dire une des méthodes testXXX
d'un TestCase), il faut utiliser une des méthodes de la classe
junit.framework.Assert, qui sont toutes accessibles au sein d'un
TestCase.
5.1.1 assertEquals
Permet de tester si deux types primitifs sont égaux
(boolean, byte, char, double, float, int, long, short). L'égalité
de deux objets peut être testée également
(attention, ce n'est pas un test sur la référence).
Pour les double et les float, il est possible de spécifier un
delta, pour lequel le test d'égalité passera quand
même.
5.1.2 assertFalse et assertTrue
Teste une condition booléenne.
5.1.3 assertNotNull et assertNull
Teste si une référence est non nulle.
5.1.4 assertNotSame et assertSame
Teste si deux Object se réfèrent on non au même
objet.
5.1.5 fail
Fait échouer le test sans condition. En cas d'utilisation
de fail, il est encore plus conseillé que pour les autres
méthodes de faire figurer un message expliquant pourquoi le
test a échoué (cf.
5.2 Personnalisation des messages d'erreur des méthodes de la
classe Assert
Les messages d'erreur peuvent être personnalisés. Les
méthodes de test existent toutes sous deux formes : l'une qui
ne prend pas de message, et l'autre qui en prend un prend au niveau
de son premier paramètre. On obtient un message de ce type :
junit.framework.AssertionFailedError: add marche mal expected:<7> but was:<8>
at junit.framework.Assert.fail(Assert.java:47)
at junit.framework.Assert.failNotEquals(Assert.java:282)
at junit.framework.Assert.assertEquals(Assert.java:64)
at junit.framework.Assert.assertEquals(Assert.java:201)
at fr.umlv.exposeJUnit.calculators.FourOpCalculatorTest.testAdd(FourOpCalculatorTest.java:45)
5.3 Fixture
: la mise en place des tests avec setUp et tearDown
Une grande partie du code d'un test unitaire sert établir
les conditions d'éxécution du dit test. Au sein d'un
même TestCase, il peut arriver que toutes les méthodes
de test aient besoin d'un minimum de chose (une connexion à
une base de données par exemple).
La mise en place de ces conditions est prévue par le
framework JUnit. Plutôt que chacun de vos tests appelle une
méthode de mise en place, puis une méthode de
nettoyage, le framework JUnit lance automatiquement avant un
test la méthode setUp, et la méthode tearDown à
son issu. Libre à vous d'implémenter ces méthodes
si vos tests ont tous besoin des mêmes choses pour fonctionner.
Prennons l'exemple d'une classe gérant la copie de fichier.
Tous les tests vont avoir besoin d'un jeu de fichiers de test,
effacés à l'issu de chaque test. Il est intéressant
dans ce cas d'implémenter dans le TestCase les méthodes
setUp et tearDown, qui vont respectivement créer le jeu de
fichiers de test et le détruire. Voici le code pour la mise en
place du test, et pour le nettoyage :
/**
* @author Cheynet J.
*
* Test case de l'opération de copie
*/
public class CopyOperationTest extends TestCase {
protected String[] filesNames = { "test0", "test1", "test2", "test3", "test4", "test5" };
protected File testFolder;
/**
* Constructor for CopyOperationTest.
* @param arg0
*/
public CopyOperationTest(String arg0) {
super(arg0);
}
/*
* @see TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
testFolder = new File("argonaute_test_CopyOperation");
testFolder.mkdir();
for (int i = 0; i < filesNames.length; i++) {
File newFile = new File(testFolder.getPath() + "/" + filesNames[i]);
newFile.createNewFile();
}
}
/*
* @see TestCase#tearDown()
*/
protected void tearDown() throws Exception {
super.tearDown();
for (int i = 0; i < filesNames.length; i++) {
File newFile = new File(testFolder.getPath() + "/" + filesNames[i]);
newFile.delete();
}
testFolder.delete();
}
...
}
6 Compilation et tests unitaires dans une même opération
avec les TestSuites et Ant
Le test permet de savoir si le code fait bien ce qu'on lui
demande. La compilation permet de savoir si la syntaxe du code est
correcte. Pourquoi ne pas compiler et tester dans un même
opération ? C'est ce qu'il est possible de faire avec Ant et
un TestSuite qui va nous permettre d'agréger tous les tests
créés.
6.1 TestSuite
Réaliser un groupement de tests est simple. La classe dite
"TestSuite" n'hérite pas en fait de TestSuit. Elle
définit une méthode publique, nommée "suite",
renvoyant un objet de type Test qui peut contenir un TestCase ou un
TestSuite. Attention, pour réaliser vos "TestSuite",
vous devez reprendre le même prototype de fonction. A noter que
là encore, Eclipse propose un Wizard automatisant la création
du TestSuite.
/*
*
Created on 28 oct. 2003 at 20:01:55 by Jerome
*
*/
package
fr.umlv.exposeJUnit.calculators;
import
junit.framework.Test;
import
junit.framework.TestSuite;
/**
*
@author Jerome
*
*/
public
class AllTests
{
public
static Test
suite() {
TestSuite
suite =
new
TestSuite("Suite
de teste pour les outils de calcul");
//$JUnit-BEGIN$
suite.addTest(new
TestSuite(FourOpCalculatorTest.class));
suite.addTest(new
TestSuite(ExtendedCalculatorTest.class));
//$JUnit-END$
return
suite;
}
}
6.2 Script
Ant
Voici le script Ant pour la pseudo-application de calculatrice. La
dernière tache "runtests" lance la classe
JUnit.textui.TestRunner avec java et avec pour paramètre le
TestSuite AllTests, qui rassemble tous les tests unitaires du projet.
<?xml
version="1.0" encoding="ISO-8859-1" ?>
<project
name="Sample.Project"
default="runtests"
basedir=".">
<property
name="app.name"
value="sample"
/>
<property
name="build.dir"
value="build/classes"
/>
<property
name="jar.junit"
value="C:\Program
Files\eclipse\plugins\org.junit_3.8.1\junit.jar"/>
<target
name="JUNIT"
description="Tester
si le classpath contient bien le jar de JUnit">
<available
property="junit.present"
classname="junit.framework.TestCase"
/>
</target>
<target
name="compile"
depends="JUNIT"
description="Compiler
les sources, sans compiler les tests">
<mkdir
dir="${build.dir}"/>
<javac
srcdir="src/main/"
destdir="${build.dir}"
>
<!--
Rq : ** pour parcourir tous les sous repertoires -->
<include
name="**/*.java"/>
</javac>
</target>
<target
name="jar"
depends="compile"
description="Créer
un jar contenant seulement les classes normales (pas celles de
test)">
<mkdir
dir="build/lib"/>
<jar
jarfile="build/lib/${app.name}.jar"
basedir="${build.dir}"
includes="fr/**"/>
</target>
<target
name="compiletests"
depends="jar"
description="Créer
le jar de l'application et compiler les tests dans le répertoire
build/testcases">
<mkdir
dir="build/testcases"/>
<javac
srcdir="src/test"
destdir="build/testcases">
<classpath>
<pathelement
location="build/lib/${app.name}.jar"
/>
</classpath>
<include
name="**/*.java"/>
</javac>
</target>
<target
name="runtests"
depends="compiletests"
if="junit.present"
description="Compiler
l'appli, créer le .jar, compiler les tests et lancer tous les
tests du TestSuite *AllTests* en mode console">
<java
fork="true"
classname="junit.textui.TestRunner"
taskname="junit"
failonerror="true">
<arg
value="fr.umlv.exposeJUnit.calculators.AllTests"/>
<classpath>
<pathelement
path="${java.class.path}"
/>
<pathelement
location="build/lib/${app.name}.jar"
/>
<pathelement
location="${jar.junit}"/>
<pathelement
location="build/testcases"
/>
</classpath>
</java>
</target>
</project>
6.3 Résultat
La console nous affiche alors les résultats de la
compilation et ceux des tests unitaires :
Buildfile:
C:\Documents and Settings\jerome\Mes
documents\junits\workspace_junit\calculatrice_Ant\build.xml
JUNIT:
compile:
[javac]
Compiling 2 source files to C:\Documents and Settings\jerome\Mes
documents\junits\workspace_junit\calculatrice_Ant\build\classes
jar:
[jar]
Building jar: C:\Documents and Settings\jerome\Mes
documents\junits\workspace_junit\calculatrice_Ant\build\lib\sample.jar
compiletests:
[javac]
Compiling 3 source files to C:\Documents and Settings\jerome\Mes
documents\junits\workspace_junit\calculatrice_Ant\build\testcases
runtests:
[junit]
.......
[junit]
Time: 0,02
[junit]
[junit]
OK (7 tests)
[junit]
BUILD
SUCCESSFUL
Total
time: 4 seconds
7 Pour
aller plus loin
Maintenant que vous savez ce qu'est JUnit, comment écrire
un test, une suite de test, et utiliser le tout avec Ant, vous pouvez
songer à améliorer votre connaissance des test
unitaires au travers des 3 pistes suivantes.
7.1 Réaliser un bon test
"If the programmer writes bad code to begin with, how can
you expect anything of better quality in the tests?", Marc
Clifton
Nous venons de voir des tests simples, où l'on attend un
résultat spécifique du code testé. Réaliser
ce genre de test peut être très utile et suffisant dans
la plupart des cas.
Néanmoins certaines personnes ne se satisfont pas d'un seul
test de ce genre par méthode. En effet, cela ne permet de dire
qu'une chose, c'est que le code fait ce qu'on attend de lui lorsqu'on
lui passe les mêmes paramètres que ceux du test.
7.1.1 Un
mauvais test
Si vous testez une des méthodes
d'une calculatrice, qui calcule la puissance au carré d'un
nombre, et que vous faites le test pour 2^2, vous faites un test qui
attend le nombre 4. Mais un tel test signifie à la fois peu et
beaucoup : beaucoup, parce que la méthode semble faire ce
qu'on lui demande. Et peu, parce que l'addition et la multiplication
auraient renvoyé le même résultat.
C'est pourquoi faire des tests
unitaires ne se limite pas forcément à tester une
expression pour deux raisons.
On vient de voir la première :
un code peut produire l'expression attendue par "hasard".
La deuxième qu'on va voir : en
testant une expression, on ne teste qu'un seul "chemin".
7.1.2 Un autre mauvais test
Prenons l'exemple d'une classe
permettant de copier un fichier en local et en distant. Si vous
testez à un niveau trop haut, c'est à dire que vous ne
testez pas toutes les méthode privées qui constituent
cette classe, vous allez devoir tester deux fonctionnalités :
la copie en local, et la copie en distant, qui constituent deux
"chemins". Si vous perdez de vue les 2 chemins possibles,
vous n'allez tester que la copie en local, ou bien, que la copie en
distant. C'est pourquoi il faut écrire un test par "chemin".
Vous trouverez des choses intéressantes
sur les améliorations possibles des outils de tests unitaires,
applicables également à vos propres tests, sur la page
Advanced Unit Test, Part V - Unit Test Patterns de Marc Clifton. La
première partie (Part I) de l'article est aussi très
intéressante : elles permet entre autre de se convaincre de
l'utilité des tests unitaires, suivant des exemples plus
classiques (sans le "test driven developpement" de
l'Extreme Programming que les auteurs de JUnit expliquent en même
temps que les tests unitaires).
7.2 Les "Mock Objects"
(les imitations d'objets)
Comment faire si pour un test, vous
avez besoin d'un objet qui n'est pas encore codé ? Comment
faire si un test doit faire appel à une base de donnée
et que la connexion est lente ?
Vous pouvez essayer de faire vous même
un nouvel objet qui simulera le comportement de l'objet réel
(inexistant, trop lent, ou trop compliqué à
instancier). Cet objet sera une "imitation" d'objet. Il
peut être intéressant que cet objet réalise des
tests sur les arguments reçus, qu'une méthode de cet
objet renvoie quelque chose de différent à chaque
appel, ... Tous ces comportements sont gérés par les
frameworks de réalisation des Moke Objects. De même que
pour les tests unitaires, chaque langage dispose de son framework
d'imitation d'objets. En java il existe en particulier :
www.mockobjects.com/ ,
http://mockcreator.sourceforge.net/
, http://www.mockmaker.org/
7.3 Les outils de tests unitaires en dehors de JUnit
7.3.1 Pour les langages .NET
http://www.nunit.org
On retrouve dans Nunit les possibilités
de JUnit , avec en plus la possibilité de faire des tests de
performances facilement.
7.3.2 Pour
C++
http://cppunit.sourceforge.net
Si vous avez Visual C++ 6, essayez
cppunit.
8 Quelques liens sur les tests unitaires
Il n'y est pas question des tests
unitaires avec JUnit, mais avec NUnit (pour .NET). Cette page
présente l'avantage de ne pas être écrite par
l'un des père de l'Extreme Programming.
|