Adloun

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 » @WebMvcTest et @DataJpaTest de Spring Boot.
  • Écrire des tests d'intégration avec @SpringBootTest et MockMvcTester, 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.

Définition 7.1Les trois niveaux de test
  • 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 :

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);
    }
}
Proposition 7.2Structure d'un test

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.

Définition 7.3Doublure

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 cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →