JUnit.org

1 Résumé

2 Introduction

  • 2.1 Le génie logiciel
    ou l'atteinte des objectifs
    que l'on s'est fixé
  • 2.2 Le développement...
    là où le framework JUnit
    peut vous aider à atteindre
    vos objectifs
  • 2.3 Pourquoi
    faire tout ça ?
    (pourquoi travailler plus,
    je n'ai pas le temps !)
  • 2.4 Efficacité, efficacité...
    Y'a t il un rapport avec
    l'Extreme Programming ?
  • 3 L'écriture
    d'un test unitaire simple
    pour une classe simple

  • 3.1 Le code à tester
  • 3.2 Génération
    ou création
    de la classe du test
    unitaire
  • 3.3 Ecriture
    du test unitaire
  • 4 Lancement
    du test unitaire

  • 4.1 Eclipse
  • 4.2 En ligne
    de commande
  • 5 Ecrire
    un test plus complexe

  • 5.1 La classe Assert
  • 5.2 Personnalisation
    des messages d'erreur
    des méthodes
    de la classe Assert
  • 5.3 Fixture :
    la mise en place des tests
  • 6 Compilation
    et tests unitaires
    dans une même
    opération avec
    les TestSuites et Ant

  • 6.1 TestSuite
  • 6.2 Script Ant
  • 6.3 Résultat
  • 7 Pour aller plus loin

  • 7.1 Réaliser un bon test
  • 7.2 Les "Mock Objects"
    (Les imitations d'objets)
  • 7.3 Les outils de tests
    unitaires en dehors de JUnit
  • 8 Quelques liens
    sur les tests unitaires

    JUnit

    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.


    Jérôme Cheynet - Version du 29-02-04