Framework JUnitCette page présente le fonctionnement de JUnit : le lancement des méthodes à tester, les procédures de contrôle, mais également les méthodes permettant d'initialiser et de nettoyer les données de test.

Mise en place

Pour intégrer JUnit à un projet existant, il faut ajouter la dépendance à JUnit dans le pom.xml du projet :

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

Il est également possible d'ajouter le jar au build path du projet.

Pour chaque classe que nous souhaitons tester, il faut créer une nouvelle classe de test.

Par convention, si nous voulons tester la classe Traitement qui se trouve dans le source folder "src/main/java" : package fr.julien.formation, nous allons créer :

  • un second source folder "src/test/java"
  • le même package fr.julien.formation dans le nouveau source folder
  • La classe test sera dans ce package et s'appelera TraitementTest
Création d'un test

La classe de test

Pour indiquer qu'une méthode correspond à un test, il suffit d'utiliser l'annotation @Test

@Test
public void test() {
}

Les assersions

Pour valider les résulats, nous allons utiliser la classe Assert qui permet de réaliser des contrôles. Si un des contrôles effectués par assert échoue, le test échoue

Méthode Comportement
Assert.assertEquals(int a, int b) Le contrôle est correct si a==b
Assert.assertEquals(double a, double b, double c) Le contrôle est correct si |b-a|<c
Assert.assertEquals(Object a, Object b) Le contrôle est correct si a.equals(b)
Assert.assertTrue(boolean a) Le contrôle est correct si a est true
Assert.assertFalse(boolean a) Le contrôle est correct si a est false
Assert.fail() Si nous exécutons ce bloc, le test échoue

Méthodes d'initialisation et de terminaison

Afin de mettre en place l'environnement d'exécution des tests, il est possible d'écrire des méthodes qui sont exécutées avant et après le test. Ces méthodes sont annotées grâce aux annotations définies ci-dessous

Annotation Execution
@Before Exécutée avant chaque méthode préfixée par @Test
@BeforeClass Exécutée une fois avant l'exécution de la classe de test
@After Exécutée après chaque méthode préfixée par @Test
@AfterClass Exécutée une fois après l'exécution de la classe de test
Lancement du test

Pour lancer un test avec eclipse :

  • Clic droit sur le test à lancer (ou sur la package)
  • Run As
  • JUnit Test
Lancer un test
Exemple

Nous allons tester la classe Traitement qui possède une fonction compterLettres() qui permet de retourner le nombre de lettres d'une chaine de caractères :

package fr.julien.formation;

import java.util.regex.Pattern;

public class Traitement {
    
	private static final Pattern PATTERN_NON_LETTRES = Pattern.compile("[^A-Za-z]");

	public int compterLettres(String entree) {
		return PATTERN_NON_LETTRES.matcher(entree).replaceAll("").length();
	}
}

Créer un test unitaire revient à créer une classe qui appelle la méthode compterLettres et qui valide le résultat du comptage pour certains cas. Nous allons créer le test qui nous assure que le comptage retourne :

  • 0 pour la chaine "123"
  • 5 pour la chaine "azert"
  • 2 pour la chaine "01az23"
package fr.julien.formation;

import org.junit.Assert;
import org.junit.Test;

public class TraitementTest {

	@Before
	public void avant(){
		System.out.println("Début du test");
	}
	
	@After
	public void apres(){
		System.out.println("Fin du test");
	}

	@Test
	public void testerComptage() {
		Traitement t = new Traitement();
		try {
			Assert.assertEquals(0, t.compterLettres("012"));
			Assert.assertEquals(5, t.compterLettres("azert"));
			Assert.assertEquals(2, t.compterLettres("01az23"));
		}
		catch (Exception e) {
			e.printStackTrace();
			Assert.fail();
		}
	}

}
Quelques recommandations

Ecrire des tests simples

Les test unitaires doivent être simples à comprendre, à exécuter, à modififier, ainsi les test unitaires doivent respecter les contraintes suivantes

  • Les tests unitaire ne contiennent pas de javadoc ni de documentation, s'il y a des points complexes c'est que le test unitaire est mal écrit
  • Il n'y a pas de conception à mettre en place lorsqu'on créé des tests unitaires : on n'utilise pas d'héritage entre les tests par exemple. De manière générale, une classe de test ne référence jamais une autre classe de test
  • Il est possible de créer des services permettant de faciliter l'écriture des tests, mais le service ne sera pas lui même un test

Ecrire des tests rejouables

Le test n'a pas une durée de vie limitée, il doit pouvoir être joué indéfiniement. C'est un point souvent négligé mais il faut penser que le test doit être valable quel que soit la date ou le jour d'exécution.

Une erreur fréquente est la suivante : la méthode AccesCalendrier.getAnneeCourante permet de récupérer l'année courante :

package fr.julien.formation;

import java.text.SimpleDateFormat;
import java.util.Date;

public class AccesCalendrier {

    private static AccesCalendrier instance = new AccesCalendrier();
    private SimpleDateFormat formatAnnee = new SimpleDateFormat("yyyy");

    private AccesCalendrier() {
        super();
    }

    public int getAnneeCourante() {
        return Integer.parseInt(formatAnnee.format(new Date()));
    }

    public static AccesCalendrier getInstance() {
        return instance;
    }
}

Il est tout à fait possible de créer un test unitaire de la manière suivante

package fr.julien.formation;

import org.junit.Assert;
import org.junit.Test;

public class AccesCalendrierTest {

    @Test
    public void testerGetAnneeCourante() {
        Assert.assertEquals(2017, AccesCalendrier.getInstance().getAnneeCourante());
    }
}

Problème : l'an prochain, notre test unitaire sera en échec alors que le programme ne présentera pas d'anomalie

Dans ce cas, que faire ? Il existe plusieurs solutions

  • La première est préférable mais n'est pas forcément réalisable : il s'agit d'adapter la conception de notre application à la problématique de test. Dans le cas présent, il ne faudrait plus se baser sur la date système mais sur une date passée en paramètre au lancement de java
  • La seconde (toujours réalisable) consiste à modifier le test de manière a être moins restrictif ou à obtenir l'information d'une autre manière : soit récupérer la date à l'aide de Date() soit tester uniquement que l'année courante est supérieure à 2016 serait déjà préférable
  • Utiliser un proxy permettant de récupérer la date et simuler ce proxy dans les tests unitaires.