TDD et JUnit : Guide Complet en Java
TDD et JUnit : Guide Complet en Java
Plan
Janvier 2023
Version 1.1
Niveaux et phases
Difficultés du test ➥ Niveaux de tests
➥ Le test exhaustif est en général impossible à réaliser : Z Test unitaire = test des (petites) parties du code, séparément.
Z Test d’intégration = test d’un ensemble de parties du code qui coopèrent.
Z L’ensemble des données d’entrée est en général infini ou de très grande taille Z Test du système = test du système entier, en inspectant sa fonctionnalité.
Z La qualité du test dépend de la pertinence du choix des données de test Z Test d’acceptation = effectué par le client pour s’assurer de la conformité au besoin.
➥ Difficultés d’ordre psychologique ou culturel ➥ Phases de tests
Z Le test est un processus destructif : un bon test est un test qui trouve une erreur. Alors Z Test de régression = test réalisé pendant la maintenance aprés un changement, afin de
que l’activité de programmation est un processus constructif. s’assurer que les système continue de fonctionner correctement.
Z Test de robustesse = tester des entrées non-prévues.
Z Test sous stress = tester en conditions de surcharge.
Principe du TDD
Comment un TDD peut améliorer la qualité d’un programme ? ➥ En TDD, vous allez écrire les solutions les plus basiques possibles pour faire passer vos
tests.
➥ Élaborer les tests avant chaque partie développée
➥ Une fois que vous avez écrit un bon test avec le code le plus simple possible, vous avez fini
Z Ne pas livrer un code non testé – et vous pouvez avancer au prochain test.
Z Détecter les erreur le plus tôt possible
➥ En découpant le problème en petites parties et les tester chacune à part.
TDD : les trois bonnes règles
Z Ceci réduit le taux d’erreur et augmente le temps de développement des applications.
Z Il réduit aussi le temps de maintenance. ➥ Il y a trois règles à respecter, selon Robert Martin (un leader dans le monde de TDD)
➥ Séparation entre le code de tests et le code applicatif ❶ Écrire un test qui échoue avant d’écrire votre code lui-même.
❷ Ne pas écrire un test compliqué.
❸ Ne pas écrire plus de code que nécessaire, juste assez pour faire passer le test qui
échoue.
Présentation Chiheb Ameur ABID 11/ 63 Présentation Chiheb Ameur ABID 12/ 63
Définitions et concepts de base Définitions et concepts de base
Cycle de TDD
➥ Écrire un test Les gains du TDD
➥ Exécuter le test et constater qu’il échoue (barre rouge) ; si le verdict est en fait une erreur ➥ Le test unitaire fournit un retour constant sur les fonctions
due au fait que le code ne compile pas (en Java) car le code applicatif n’a pas encore été ➥ La qualité de la conception augmente, ce qui contribue davantage à un bon entretien
écrit, alors écrire le code applicatif minimal du point de vue du langage, et vérifier cette ➥ Le développement piloté par les tests agit comme un filet de sécurité contre les bogues
fois que le test échoue à cause de l’oracle
➥ TDD garantit que votre application répond réellement aux exigences définies pour elle
➥ Écrire le code applicatif le plus simple qui permet de faire passer le test, et seulement ce
code ➥ TDD a un cycle de vie de développement très court
➥ Lancer le test et vérifie qu’il passe (barre verte)
➥ Réusiner les tests et le code
Présentation Chiheb Ameur ABID 13/ 63 Présentation Chiheb Ameur ABID 14/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Présentation Chiheb Ameur ABID 15/ 63 Présentation Chiheb Ameur ABID 16/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
Principe
➥ Une classe de tests unitaires est associée à une autre classe
➥ Une classe de tests unitaires hérite de la classe junit.framework.TestCase pour
Les annotations de JUnit
bénéficier de ses méthodes de tests
➥ Des annotations ont été introduites dans JUnit 4 rendant le code Java plus lisible et simple.
➥ Les méthodes de tests sont identifiées par des annotations Java
Z JUnit 4 est basée sur les annotations
Z Nécessite Java 5 ou supérieur
➥ Il n’est plus nécessaire d’imposer un nom pour les méthodes de test Méthodes de tests
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Présentation Chiheb Ameur ABID 19/ 63 Présentation Chiheb Ameur ABID 20/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Instructions de test
Les annotations de JUnit 4
➥ Une annotation est désignée par un nom précédé du caractère @
➥ Une annotation précède l’entité qu’elle concerne
➥ Toutes les annotations sont définies dans le package org.junit
Z L’instruction la plus importante est fail() : les autres ne sont que des raccourcis
d’écriture !
Présentation Chiheb Ameur ABID 21/ 63 Présentation Chiheb Ameur ABID 22/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
Tests automatisés avec le framework JUnit 4 Tests automatisés avec le framework JUnit 4
Exemple simple
➥ Créer un nouveau projet Java Méthodes de test
➥ On désire tester la méthode ci-après où on a introduit volontairement une erreur (classe ➥ Une méthode de test est une méthode qui exécute un test unitaire
Example) ➥ Les méthodes de test sont annotées avec @Test
public static int somme(int a,int b,int c) { ➥ Convention de nommage d’une méthode de test test[méthode à tester]()
return a+b-c;
} Z Mais aucune obligation (nom quelconque possible)
➥ Publique, sans paramètre, type de retour void
➥ Créer un Junit Test Case implémentant le test suivant :
➥ Les principaux paramètres de l’annotation @Test
@Test
public void test() { Z expected : le test échoue si une exception n’est pas levée
int resultat= Example.somme(5,4,2);
assertEquals(11,resultat);
Z timeout : durée maximale spécifiée en millisecondes
}
➥ L’annotation @Ignore permet d’ignorer un test
➥ Tester le cas de test à partir du menu contextuel : choisir Run As -> JUnit Test
Présentation Chiheb Ameur ABID 23/ 63 Présentation Chiheb Ameur ABID 24/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
Tests automatisés avec le framework JUnit 4 Tests automatisés avec le framework JUnit 4
}
test.length(); ➥ Méthodes d’initialisation utilisée avant chaque test
Z setUp() est une convention de nommage
➥ En utilisant l’annotation @Rule Z L’annotation @Before est utilisée
Z Elle permet de vérifier certaines propriétés de l’exception levée ➥ Méthodes de finalisation utilisée après chaque test
@Rule
Z tearDown() est une convention de nommage
public ExpectedException exceptionRule = ExpectedException.none(); Z L’annotation @After est utilisée
@Test
public void whenExceptionThrown_thenRuleIsApplied() {
exceptionRule.expect(NumberFormatException.class);
exceptionRule.expectMessage("For input string");
Integer.parseInt("1a");
}
Présentation Chiheb Ameur ABID 25/ 63 Présentation Chiheb Ameur ABID 26/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Présentation Chiheb Ameur ABID 27/ 63 Présentation Chiheb Ameur ABID 28/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
❶ Écrire un test
➥ On commence par écrire un test ❷ Écrire le code applicatif
package j1exemple1; ➥ On écrit le code applicatif pour faire passer le test
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Test; package j1exemple1;
class ValidPasswordTest {
@Test public class PasswordValidator {
void test() {
PasswordValidator pv=new PasswordValidator(); public boolean isValid(String pw) {
Assert.assertEquals(true,pv.isValid("123456")); if (pw.length()>=5 && pw.length()<=10)
}
} return true;
else
➥ Pour exécuter le test, à partir du menu contextuel, choisir Run As -> JUnit Test return false;
➥ Évidemment, on est en rouge, puisque le code applicatif n’existe pas }
}
❸ Reusiner
➥ On optimise notre code de test
Z Il n’est pas nécessaire de créer une instance de la classe PasswordValidator
Z Associer un message personnalisé à l’assertion ❹ Reusiner le test
package j1exemple1; ➥ On corrige le code applicatif pour passer le test
import static org.junit.Assert.*;
import org.junit.Assert; package j1exemple1;
import org.junit.Test;
public class PasswordValidator {
class ValidPasswordTest { static public boolean isValid(String pw) {
@Test if (pw.length()>=5 && pw.length()<=10)
void test() { return true;
Assert.assertEquals("Verifier longueur mot de passe ",true,PasswordValidator.isValid("123456 else
")); return false;
} }
} }
Présentation Chiheb Ameur ABID 31/ 63 Présentation Chiheb Ameur ABID 32/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 4 Le framework JUnit 4
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
Architecture de JUnit 5
Présentation Chiheb Ameur ABID 35/ 63 Présentation Chiheb Ameur ABID 36/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 5 Le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
assertTrue(actualMessage.contains(expectedMessage));
}
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
➥ Nous écrivons un premier test permettant de vérifier que l’étagère est initialement vide
@Test
public void emptyBookShelfWhenNoBookAdded() {
BookShelf shelf = new BookShelf();
List<String> books = shelf.books();
assertTrue(books.isEmpty(), () -> "BookShelf should be empty.");
}
Présentation Chiheb Ameur ABID 39/ 63 Présentation Chiheb Ameur ABID 40/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 5 Le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
Exemple de TDD avec JUnit 5 ➥ Est-il possible de modifier la liste retournée par la méthode books() ?
Z Violer le principe d’encapsulation ! ?
➥ Modifier le code applicatif pour passer au vert
➥ Écrire un test pour vérifier cette contrainte
import java.util.ArrayList;
import java.util.List; @Test
public class BookShelf { void booksReturnedFromBookShelfIsImmutableForClient() {
private final List<String> books = new ArrayList<>(); BookShelf shelf = new BookShelf();
public List<String> books() { shelf.add("Effective Java");
return books; shelf.add("Code Complete");
} List<String> books = shelf.books();
public void add(String bookToAdd) { try {
books.add(bookToAdd); books.add("The Mythical Man-Month");
} fail(() -> "Should not be able to add book to books");
} } catch (Exception e) {
assertTrue(e instanceof UnsupportedOperationException, () -> "Should throw
UnsupportedOperationException.");
}
}
Présentation Chiheb Ameur ABID 43/ 63 Présentation Chiheb Ameur ABID 44/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 5 Le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
@BeforeEach
Exercice de TDD avec JUnit 5
void init() throws Exception {
shelf = new BookShelf(); ❶ En continuant l’exemple précédent, et en suivant une démarche de TDD, ajouter la
} fonctionnalité permettant de trier la liste des livres selon l’ordre lexicographique
@Test ❷ On veut retourner une nouvelle liste triée en gardant la liste originale dans le même ordre
public void emptyBookShelfWhenNoBookAdded() {
List<String> books = shelf.books();
assertTrue(books.isEmpty(), () -> "BookShelf should be empty.");
}
[...]
Présentation Chiheb Ameur ABID 47/ 63 Présentation Chiheb Ameur ABID 48/ 63
Tests automatisés avec le framework JUnit Tests automatisés avec le framework JUnit
Le framework JUnit 5 Le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
Tests automatisés avec le framework JUnit 5 Tests automatisés avec le framework JUnit 5
Présentation Chiheb Ameur ABID 51/ 63 Présentation Chiheb Ameur ABID 52/ 63
Tests automatisés avec le framework JUnit Tests paramétrisés
Le framework JUnit 5
Tests paramétrisés
Exemple : un seul argument
➥ Junit 5 permet les tests paramétrisés en utilisant des différentes valeurs pour la même
méthode de test ➥ Soit à tester la méthode suivante
➥ Un test paramétrisé est exécuté autant de fois que les valeurs spécifiées sur ses entrées public static boolean isOdd(int number) {
return number % 2 != 0;
➥ L’annotation @ParameterizedTest, du package org.junit.jupiter.params, }
Présentation Chiheb Ameur ABID 55/ 63 Présentation Chiheb Ameur ABID 56/ 63
Tests paramétrisés Tests paramétrisés
Présentation Chiheb Ameur ABID 59/ 63 Présentation Chiheb Ameur ABID 60/ 63
Bonnes pratiques Bonnes pratiques
Bonnes pratiques
Exercice : jeu de Tic-Tac-Toe (suite)
➥ En continuant l’exercice, on souhaite maintenant alterner le jeu entre les deux joueurs
symbolisés par les types de leurs pions (les caractères ’X’ et ’O’). On suppose toujours que
le joueur X commence le jeu. En suivant une démarche de TDD, implémenter une fonction
permettant de retourner le joueur qui va joueur à un moment donné
Tester les exceptions
Z D’abord, écrire un test pour vérifier que le joueur X commence le jeu
➥ Lister les exceptions qui doivent être levées Z Puis, écrire un deuxième test pour vérifier l’alternance entre les deux joueurs
➥ Écrire un test pour chaque exception ➥ Développer la fonctionnalité pour déterminer le vainqueur
➥ Vérifier que l’exception est bien levée et que le message est clair, compréhensible Z Définir un test qui vérifie que le jeu est en cours. La méthode play retourne l’état du
jeu à travers une chaine de caractères
Z Définir les tests pour la vérification qu’un joueur gagne. On procède en écrivant un
Ne jamais faire confiance au client de vos services test pour chaque type de condition pour annoncer le vainqueur :
➥ Le client utilise mal vos services ❶ Tous les pions sont placés sur la même ligne horizontale. Après avoir proposé une
Z Mauvais inputs, valeurs limites, arguments nuls, valeurs inattendues solution simple, ne pas oublier de réusiner le code
Z Comportements limites, pop() sur une collection vide ❷ Tous les pions sont placés sur la même ligne verticale
❸ Tous les pions sont placés sur la première diagonale
❹ Tous les pions sont placés sur la deuxième diagonale
➥ Développer la fonctionnalité qui annonce que le jeu se termine sans vainqueur. La grille
doit être initialisée.
Questions ?