Introduction aux tests en Java
Pourquoi les tests sont essentiels en Java, la pyramide des tests et un aperçu des principaux frameworks de test Java.
Les tests automatisés permettent de prouver que votre code fait ce que vous pensez qu'il fait — et de continuer à le prouver à mesure que le code évolue. Au lieu d'exécuter le programme à la main et d'inspecter le résultat manuellement, vous écrivez de petits programmes qui exercent votre code et vérifient les résultats automatiquement. En Java, cet écosystème s'articule autour de frameworks comme JUnit et Mockito, mais les idées sous-jacentes — arrange, act, assert — sont assez simples pour être écrites à la main. Ce chapitre dresse le panorama avant que les chapitres suivants approfondissent chaque outil.
Pourquoi les tests automatisés sont importants
Un test est une vérification petite et reproductible qu'un morceau de code se comporte correctement. Le bénéfice ne se mesure pas à la première exécution — il se mesure à toutes les exécutions suivantes. Une fois qu'un comportement est capturé dans un test, tout changement qui le casse échoue bruyamment et immédiatement, au lieu d'apparaître comme un bug en production des semaines plus tard. Les tests documentent aussi l'intention : un test bien nommé dit ce que le code est censé faire.
// A test names a behavior, runs the code, and asserts the outcome.
@Test
void addsTwoPositiveNumbers() {
int result = Calculator.add(2, 3);
assertEquals(5, result); // fails the build if result != 5
}L'objectif est un retour d'information rapide. Une suite de tests verte signifie que vous pouvez refactoriser en toute confiance ; une suite rouge indique précisément ce qui a cassé.
La pyramide des tests
Les tests se répartissent en couches, généralement représentées sous forme de pyramide. Les tests unitaires se trouvent en bas : nombreux, rapides, chacun vérifiant une classe ou une méthode de manière isolée. Les tests d'intégration se trouvent au milieu : moins nombreux, plus lents, vérifiant que les composants fonctionnent ensemble (votre code plus une base de données, par exemple). Les tests de bout en bout (E2E) se trouvent au sommet : peu nombreux, les plus lents, pilotant l'application entière comme le ferait un utilisateur.
| Niveau | Portée | Vitesse | Nombre | Outils Java |
|---|---|---|---|---|
| Unitaire | une classe/méthode | rapide (ms) | nombreux | JUnit, AssertJ |
| Intégration | plusieurs composants | moyenne | quelques-uns | JUnit, Testcontainers |
| Bout en bout | système complet | lente | peu | Selenium, REST-assured |
La forme compte : appuyez-vous sur des tests unitaires bon marché et rapides pour la majeure partie de la couverture, et réservez les tests E2E lents et fragiles à quelques parcours utilisateur critiques.
Le pattern arrange–act–assert
Presque chaque test, dans n'importe quel framework, suit la même structure en trois étapes. Arrange (préparer) les entrées et les dépendances. Act (agir) en appelant le code testé. Assert (vérifier) que le résultat correspond à ce que vous attendez. Maintenir ces étapes visuellement séparées rend un test facile à lire et facile à déboguer en cas d'échec.
@Test
void rejectsBlankUsername() {
// Arrange
UserService service = new UserService();
// Act
boolean valid = service.isValidUsername(" ");
// Assert
assertFalse(valid);
}Une assertion qui échoue lance une exception, le framework l'enregistre, et l'exécution continue jusqu'au test suivant — ainsi, un comportement cassé ne masque jamais les autres.
JUnit, le runner standard
JUnit est le framework de tests unitaires de facto pour Java. Vous annotez les méthodes avec @Test, JUnit les découvre par réflexion, exécute chacune d'elles, et rapporte les succès/échecs. Les assertions comme assertEquals, assertTrue et assertThrows sont des helpers statiques qui font échouer le test si l'attente n'est pas satisfaite. Les vrais projets exécutent JUnit via un outil de build (le plugin Surefire de Maven ou la tâche test de Gradle), et non à la main.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void dividesNumbers() {
assertEquals(4, Calculator.divide(8, 2));
}
@Test
void throwsOnDivideByZero() {
assertThrows(ArithmeticException.class, () -> Calculator.divide(1, 0));
}
}Comme il n'y a pas de JAR JUnit ni d'outil de build sur ce runner, l'exemple ci-dessous reconstruit la même idée from scratch — un petit harnais qui exécute des vérifications nommées et comptabilise les succès et les échecs, exactement ce que @Test plus assertEquals font en coulisses.
Ce qu'il faut retenir de cette exécution :
- Chaque appel à
assertEqualsest un cas de test — préparer les entrées, agir en appelantaddouisBlank, et vérifier le résultat — reflétant exactement ce que fait une méthode JUnit@Test. - Une vérification réussie affiche
PASSet une vérification échouée afficheFAILavec les valeurs attendue et réelle, ce qui correspond aux messages de diagnostic que donnent les assertions JUnit. - Le cas volontairement incorrect (
expected 10 but got 5) montre à quoi ressemble un test rouge : le harnais continue d'exécuter les vérifications restantes au lieu de s'arrêter au premier échec. - Le résumé comptabilise 5 au total, 4 réussis, 1 échoué — le même rapport succès/échec qu'un runner de tests affiche à la fin d'une exécution.
- Parce qu'un test a échoué, le programme se termine avec
BUILD FAILURE, démontrant pourquoi un seul test cassé devrait faire échouer l'ensemble du build en CI.
Comment les pièces s'assemblent
Les outils de test Java se superposent les uns aux autres, des assertions brutes jusqu'à l'intégration complète dans le build :
- Les assertions (
assertEquals,assertThrows) énoncent ce qui doit être vrai. - JUnit découvre et exécute les méthodes
@Testet rapporte les résultats. - Mockito fournit de faux collaborateurs afin qu'une unité puisse être testée en isolation.
- Maven ou Gradle intègre la suite dans le build, faisant échouer le build à chaque test rouge.
- La CI exécute le build à chaque push, afin que le code cassé n'atteigne jamais la branche principale.
Chaque chapitre suivant traite d'un échelon de cette échelle — les annotations et assertions JUnit en premier, puis le mocking avec Mockito, puis l'intégration des tests dans Maven et Gradle. Comprendre où se situe chaque outil rend l'ensemble de l'histoire des tests cohérente.