Tester l'application
Cours · Spring Boot : écrire le serveur (école d'ingénieur), chapitre 7 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
Depuis le chapitre 3, la Librairie a quelques tests, écrits au fil de l'eau. Ils ont déjà rendu service : au chapitre 5, ils ont prouvé que le passage à la base de données n'avait rien changé pour les clients de l'API. Mais ils ne couvrent ni les commandes enregistrées, ni les stocks, ni les annulations du chapitre précédent ; et personne ne les lance systématiquement. Or chaque chapitre à venir modifiera ce code : la sécurité, une autre base de données, la mise en production. Sans tests, chaque modification est un pari.
Ce chapitre organise les tests de la Librairie. Il présente les outils (JUnit, AssertJ, Mockito, le module de test de Spring), les trois niveaux de test (unitaire, tranche, intégration), et surtout les pièges propres aux tests d'une application Spring : un contexte partagé entre classes de test, une transaction de test qui change ce que l'on observe. Ces pièges ne sont pas inventés pour l'occasion : nous les avons rencontrés en écrivant les tests de ce chapitre, et nous montrons les rapports d'échec réels.
Objectifs du chapitre
- Écrire des tests unitaires avec JUnit 5 et AssertJ, sans Spring.
- Isoler une classe de ses dépendances par des doublures Mockito.
- Tester une seule couche à la fois, avec les « tranches »
@WebMvcTestet@DataJpaTestde Spring Boot. - Écrire des tests d'intégration avec
@SpringBootTestetMockMvcTester, et garder les tests indépendants les uns des autres. - Lancer la suite et lire un rapport d'échec.
Le projet du chapitre est code/ch7/librairie ; son code est celui du chapitre précédent, seuls ses tests (dossier src/test/java) sont nouveaux.
7.1 Pourquoi, quoi et comment tester
Un test automatique est un petit programme qui exécute une partie de l'application et vérifie le résultat. On l'écrit une fois ; il s'exécute ensuite à chaque modification, en quelques secondes, et signale immédiatement ce qui a cessé de fonctionner. Il sert aussi de documentation : un test bien nommé dit ce que le code est censé faire.
- Test unitaire : une classe, isolée, sans Spring ni base. Il s'exécute en millisecondes.
- Test de tranche : une couche de l'application (les contrôleurs, ou les dépôts), avec la partie de Spring qui la fait fonctionner ; le reste est simulé ou absent.
- Test d'intégration : l'application entière, de la requête HTTP à la base de données. C'est le plus proche de la réalité, mais aussi le plus lent.
On écrit beaucoup de tests unitaires, moins de tests de tranche, et quelques tests d'intégration : c'est la pyramide des tests.
Les outils sont déjà là. Chaque starter de Spring Boot 4 a son pendant de test, qu'Initializr ajoute avec la portée test ; la Librairie en a trois :
spring-boot-starter-webmvc-test
spring-boot-starter-validation-test
spring-boot-starter-data-jpa-test
Ils apportent :
- JUnit 5 (JUnit Jupiter) : le moteur qui trouve et exécute les méthodes
@Test; - AssertJ : les vérifications, sous forme de phrases (
assertThat(stock).isEqualTo(7)) ; - Mockito : les doublures (section 7.3) ;
- Spring Test : le chargement de l'application dans un test, et
MockMvcTesterpour lui envoyer des requêtes HTTP sans démarrer de serveur.
Par convention, les classes de test se terminent par Test, vivent dans src/test/java, dans le même paquet que la classe testée, et ./mvnw test les exécute toutes.
7.2 Tests unitaires
Le code métier de la Librairie, le calcul d'un total et la gestion du stock, vit dans des classes ordinaires. Les tester ne demande que de créer des objets et d'appeler leurs méthodes :
/** Tests UNITAIRES : de simples objets Java, sans Spring ni base de données. */
class CommandeTest {
static Livre livre(String titre, String prix, int stock) {
return new Livre("9782956300014", titre, "Auteur", "Rayon", new BigDecimal(prix), null, false, stock);
}
@Test
void leTotalAdditionneLesLignes() {
Commande commande = new Commande("Ada", "ada@example.org", Retrait.BOUTIQUE);
commande.ajouterLigne(new LigneCommande(livre("Le Petit Prince", "8.50", 25), 2));
commande.ajouterLigne(new LigneCommande(livre("L’Étranger", "7.90", 9), 3));
assertThat(commande.getTotal()).isEqualByComparingTo("40.70");
assertThat(commande.getLignes()).hasSize(2)
.allSatisfy(ligne -> assertThat(ligne.getCommande()).isSameAs(commande));
}
@Test
void uneVenteDiminueLeStock() {
Livre livre = livre("Programmation Java", "32.00", 12);
livre.retirerDuStock(5);
assertThat(livre.getStock()).isEqualTo(7);
}
@Test
void onNeVendPasPlusQueLeStock() {
Livre livre = livre("Algorithmique", "89.00", 3);
assertThatThrownBy(() -> livre.retirerDuStock(4))
.isInstanceOf(StockInsuffisantException.class)
.hasMessage("Stock insuffisant pour « Algorithmique » (3 en stock).");
assertThat(livre.getStock()).isEqualTo(3);
}
}
Une méthode @Test, sans paramètre ni résultat, dont le nom décrit le comportement attendu. Son corps suit trois temps : préparer (créer les objets), agir (appeler la méthode testée), vérifier (des assertions). Le test échoue si une assertion est fausse ou si une exception inattendue s'échappe.
Les assertions d'AssertJ commencent toutes par assertThat(…), suivie de vérifications adaptées au type de la valeur : égalité, taille d'une liste, contenu, condition vérifiée par chaque élément… Pour les montants, on utilise la méthode isEqualByComparingTo, qui compare deux BigDecimal par leur valeur : 40.7 et 40.70 sont égaux pour elle, alors que equals les distingue par leur nombre de décimales. assertThatThrownBy vérifie qu'une action lève l'exception attendue, avec le message attendu.
7.3 Isoler une classe : les doublures
Le service des commandes dépend de deux dépôts, donc de la base. Pour le tester seul, on remplace ses dépendances par des doublures.
Une doublure (test double, souvent appelée mock) est un objet qui remplace une vraie dépendance dans un test : on lui dicte ses réponses (« si on te demande le livre 9782956300052, renvoie celui-ci »), et on vérifie ensuite comment elle a été utilisée (« save a été appelée une fois »). Mockito fabrique des doublures de n'importe quelle interface ou classe.
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.