Aller au contenu

JUnit 5 (ou 4)

Objectifs

Dans la pratique courante, les tests unitaires permettent de détecter les failles dans la logique de programmation afin d’améliorer la qualité du code. Les tests unitaires sont utilisés pour s’assurer que le code fonctionnera comme prévu en cas de changements ultérieurs. L’objectif consiste à vérifier que les fonctionnalités développées dans notre application retourne les résultats attendus !

Un framework pour les tests : JUnit

JUnit est un framework de tests unitaires pour Java. Les IDE comme IntelliJ IDEA, Eclipse, NetBeans et Visual Studio Code et les et les outils de compilation (Gradle, Maven et Ant) prennent en charge JUnit en mode Debug.

Versions

JUnit 5, la dernière version, fonctionne avec Java 8 ou plus.

JUnit 4 est encore utilisé.

De petites différences de syntaxe existent entre la version 4 et version 5.

Structure des tests et couverture

Pour organiser le code, un projet possède généralement deux sources :

img

Pour chaque classe du projet à tester, on va créer une classe portant le même nom avec le suffixe Test.

Attention ! Il faut la même structure de package dans les 2 environnements.

RelevesNotes.java -> RelevesNotesTest.java

On va aussi créer pour chaque méthode de la classe à tester une (ou des) méthode(s) dans la classe de test.

Le but est de couvrir la majorité du code, c’est-à-dire que d’une part chaque méthode non triviale soit testée (les getters sont généralement des méthodes triviales) et d’autre part, qu’à l’intérieur d’une méthode testée, on couvre tous les cas pouvant se produire.

Dans l’exemple ci-dessous, il y aurait 5 cas à tester :

public static Cellule cellule(String cell) {
		x = ++x % xMax;

    if(cell.startsWith("C")) {
			int size = Integer.parseInt(cell.substring(1));
			fourmiList = new ArrayList<Fourmi>(size);

			for (int i = 0; i<size; i++) {
				fourmiList.add(new Fourmi(x, y));
			}

			return new Cellule(true, size);
		}
		else if (cell.equals("0")) {
			return new Cellule();
		}
		else if (cell.equals("-1")) {
			return new Cellule(false);
		}
		else if (cell.matches("[0-9]*")) {
			return new Cellule(Integer.parseInt(cell));
		}
		else {
			throw new UnsupportedOperationException("code cellule inconnu : "+cell);
		}
	}

Zoom sur les Asserts

Les annotations

Comme vous l’avez déjà remarqué à plusieurs reprises avec Java et SpringBoot, nous utilisons de nombreuses annotations.

Une annotation permet de marquer certains éléments du langage Java afin de leur ajouter une propriété particulière. Ces annotations peuvent ensuite être utilisées à la compilation ou à l’exécution (grâce à l’API de réflection java.lang.reflect) pour automatiser certaines tâches…

Nous pourrions créer nos propres annotations mais ce n’est pas le sujet de ce cours dans lequel nous allons utiliser des annotations pour les Tests Unitaires ! Souvenez-vous que les annotations peuvent porter sur différents éléments comme des classes, des méthodes, des interfaces, des constructeurs, des variables…

Diriger les Tests

JUnit utilise des annotations personnalisées pour diriger l’exécution des tests.

Voici une liste non exhaustive des annotations utilisées :

Comment ça marche

L’API de réflection va permettre au moteur d’exécution de JUnit de définir le rôle de chaque méthode et de les exécuter au bon moment.

http://junit.org/junit/javadoc/4.5/index.html

4 Etapes

Les 4 étapes correspondent à l’exécution des méthodes :

Illustration :

images/annotations-tests.png

Principes de base

JUnit propose une API (un ensemble de méthodes permettant de réaliser des vérifications). La principale fonction est assert().

Au début du fichier de test, on importe les fonctions utilisées :

import org.junit.Test;
import static org.junit.Assert.*;

asserts

Tous les tests fonctionnent à peu près de la même façon, on veut que dans un état donné, le code produise un résultat attendu. On compare donc le résultat attendu (expected) avec le résultat obtenu en appelant la méthode à tester (actual).

@Test(expected=java.lang.IndexOutOfBoundException.class)

Exemple

Il faut donc d’abord initialiser les variables pour arriver à l’état que l’on souhaite (ce qui impose parfois de refactorer un peu le code à tester) avant de pouvoir lancer le test.

Les méthodes de test sont public, void et ne prennent pas de paramètres.

Il faut comparer un résultat de même type que celui produit par le retour de la méthode à tester :

@Test
public void testGetPlusHauteDisponible() { // la méthode à tester renvoie un int
  Puissance4 app = new Puissance4();
      char[][] plateau = {
          {' ','X',' ',' ',' ',' ', ' '},
          {' ','O',' ',' ',' ',' ', ' '},
          {' ','O',' ',' ',' ',' ', ' '},
          {'O','X',' ',' ',' ',' ', ' '},
          {'X','O',' ',' ',' ',' ', ' '},
          {'O','X','X',' ',' ',' ', ' '}
          };
      app.init(plateau);

      assertEquals(2, app.getPlusHauteDisponible(0)); // on commence à compter d'en haut
      assertEquals(4, app.getPlusHauteDisponible(2));
      assertEquals(5, app.getPlusHauteDisponible(3));

}
@Test
public void testColonnePleine() { // la méthode à tester renvoie un boolean
  Puissance4 app = new Puissance4();
      char[][] plateau = {
          {' ','X',' ',' ',' ',' ', ' '},
          {' ','O',' ',' ',' ',' ', ' '},
          {' ','O',' ','O',' ',' ', ' '},
          {'O','X','O',' ',' ',' ', ' '},
          {'X','O',' ',' ',' ',' ', ' '},
          {'O','X',' ',' ',' ',' ', ' '}
          };
      app.init(plateau);

      assertFalse(app.colonnePleine(0));
      assertTrue(app.colonnePleine(1));
}

Dans l’exemple précédent, on a couvert plusieurs cas dans une même méthode. On pourrait aussi faire deux méthodes de test séparées.

on utilise assertThrows pour attendre un type d’exception particulier.

@Test
	void testMinimumListeVide() {
		Assertions.assertThrows(NoSuchElementException.class, () -> {
			releveVide.min();
		});
	}

Autres exemples sur la documentation officielle

Notes sur le principe des Annotations en java

En général, les annotations sont placées devant la délcaration de l’élément qu’elle marque.

annotations

Les annotations permettent d’ajouter des metas informations sur des méthodes :
https://www.javatpoint.com/java-annotation

Méta-annotations

Exemple de création d’une annotation TODO

création annotation

Exemple de l’utilisation de notre annoatation personnalisée :

exemple

Tout ça pour dire que JUnit utilise des annotations personnalisées pour diriger les tests !

Intégration avec eclipse/gradle

L’api JUnit contient quelques annotations qu’il faut utiliser pour que les tests soient exécutés (expliqué plus haut) :

Autres annotations sur la documentation officielle.

JUnit n’est pas nativement intégrée dans java, il faut donc l’ajouter aux dépendances du projet.

On peut le faire directement dans les propriétés du projet, ou mieux dans le build.gradle :

dependencies {
    // Use JUnit Jupiter API for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'

    // Use JUnit Jupiter Engine for testing.
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}

Pour exécuter une classe de test, il faut selectionner run as… junit test case.

On peut alors visualiser (eclipse, IntelliJ, …) les résultats graphiquement :

img

Les resultats (pour les non daltoniens) sont de 3 couleurs :

Si on clique sur un test, on obtient le détail (ce qui était attendu et ce que l’on a obtenu) :

img

La méthode TDD

La méthode TDD, c’est écrire les tests dès le début puis coder pour que les tests passent. Voici les étapes d’un cycle de développement en TDD :

  1. Write a test (écrire un test)
  2. Run all tests (lancer les tests)
  3. Write the implementation code (écrire le code d’implémentation)
  4. Run all tests (lancer les tests)
  5. Refactor (adapter, modifier)

Plus de détail ici.

Voici d’autres sources :

https://howtodoinjava.com/junit5/junit-5-assertions-examples/

exemples et exercices

Problème avec Eclipse et la librairie JUnit

Dans ce cas, vous avez créé un projet java avec Eclipse sans Maven ni Gradle et par conséquent vous devez ajouter la librairie JUnit au sein de votre projet. C’est simple et ça fonctionne tout seul avec la solution ci-dessous !

librairie junit

Si vous avez des erreurs sur le projet Ferme :

c’est parce qu’Eclipse ne reconnait pas JUnit pour l’éxécution, il faut l’ajouter dans la librairie du projet.

Solution: ajouter la librairie JUnit5 library au BuilPath du projet :

Click droit sur le projet puis build path -> configure buildpath -> add libraries -> add JUnit et c’est bon !

Lien vers un site pour apprendre

https://howtodoinjava.com/junit5/junit5-test-suites-examples/