Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Apprendre la Programmation Orientée Objet avec le langage Java
  3. Les tests
Extrait - Apprendre la Programmation Orientée Objet avec le langage Java (avec exercices pratiques et corrigés) (3e édition)
Extraits du livre
Apprendre la Programmation Orientée Objet avec le langage Java (avec exercices pratiques et corrigés) (3e édition) Revenir à la page d'achat du livre

Les tests

Introduction

Quand un client commande un développement, un cahier des charges est rédigé décrivant des fonctionnalités de haut niveau sous forme de "cas concrets d’utilisation". C’est ce document qui servira plus tard à valider les développements effectués. Ces cas d’utilisation vont mettre en action de nombreux objets développés par l’équipe. Ces objets vont communiquer entre eux avec des méthodes et des chronologies mûrement réfléchies pendant l’analyse. Chaque échange s’effectuera la plupart du temps avec des paramètres "aller" et "retour". Les rangs admissibles de ces paramètres seront connus et ces objets fonctionneront généralement parfaitement bien quand ils recevront ce qu’ils attendront au moment où ils les attendront. Mais que se passera-t-il quand le cadencement s’emballera ou que les paramètres passés seront hors limites ?

La solidité d’une application se révèle dans les cas extrêmes par des traitements adaptés des défauts et une bonne protection des données. Pour gagner ce degré de fiabilité, il faut avant tout que chaque maillon de la chaîne reste stable, quelles que soient ses conditions d’exploitation. Pour cela, il faut les éprouver...

Environnement d’exécution des tests unitaires

Il est toujours possible d’écrire des "petites applications" autonomes permettant d’éprouver les objets de la future "grande application". Par exemple, un code chargé dans une console pourra instancier la classe à tester puis dérouler une série d’appels affichant des messages d’erreur ou écrivant les résultats dans un fichier. C’est tout à fait possible, mais… pas très pratique.

IntelliJ IDEA avec l’environnement de tests Java JUnit 5 simplifient la rédaction, l’exécution et l’analyse des tests unitaires. Pas besoin de "petites applications" autonomes ; IntelliJ IDEA propose directement la préparation d’un ensemble de tests que le développeur pourra jouer en totalité, en groupe (playlist) ou individuellement grâce à l’explorateur de tests. Les résultats des tests sont synthétisés dans une vue type "arbre" utilisant les couleurs jaune et vert et permettant d’aller rapidement en cas d’échec sur la ligne de code défaillant. Il est possible d’exécuter les tests en mode Debug et donc de "tracer" les méthodes appelées dans les objets à fiabiliser.

Ce chapitre ne traite que d’une petite partie de JUnit...

Le projet avec tests unitaires

Les tests sont des méthodes de classes que l’on ajoute, la plupart du temps, au projet contenant les classes à tester, en s’aidant des assistants d’IntelliJ IDEA.

 Faisons un premier projet, toujours de type console, dénommé… DemoTests ayant comme nom de package demo.tests.

 Ajoutons à ce projet une classe Vecteur ayant le contenu suivant :

package demo.tests; 
 
public class Vecteur { 
 
    private int x; 
    private int y; 
 
    public int getX() { 
        return x; 
    } 
 
    public void setX(int x) { 
        this.x = x; 
    } 
 
    public int getY() { 
        return y; 
    } 
 
    public void setY(int y) { 
        this.y = y; 
    } 
 
    public void Ajouter(Vecteur v) 
    { 
        x += v.x; 
        y += v.x;   //Erreur de codage 
        // aurait dû être y += v.y;  
    } 
} 

La fonctionnalité de cette classe est très simple. On y trouve deux propriétés...

La classe de tests

 Placez votre souris sur la classe Vecteur et cliquez sur More Actions.

images/09R01IV3.png

 Sélectionnez Create Test.

images/09R02IV3.png

 Validez l’insertion des tests dans la même racine.

images/09R03IV3.png

 Dans l’assistant de création de tests, sélectionnez la bibliothèque JUnit5 puis cliquez sur le bouton Fix pour démarrer son installation dans le projet.

images/09R04IV3.png

 Nommez la classe de tests VecteurTest et gardez le package demo.tests en tant que Destination package.

 Sélectionnez la méthode Ajouter comme méthode à tester.

images/04RI09V2.png

 Ouvrez la classe VecteurTest et utilisez l’assistant pour effectuer les ajouts nécessaires.

images/09R05IV3.png

Le code de départ de notre classe de test est le suivant :

package demo.tests; 
import org.junit.jupiter.api.Test; 
 
import static org.junit.jupiter.api.Assertions.*; 
 class VecteurTest { 
 
    @Test 
    void ajouter() { 
    } 
} 

On y trouve le package contenant la classe VecteurTest qui elle-même contient la méthode ajouter.

Chaque méthode de tests doit être précédée par l’attribut @Test qui la distinguera d’éventuelles autres méthodes de la même classe.

Il se peut que l’assistant ait défini totalement le chemin du package Test en écrivant...

Contenu d’une méthode de test

Le test va être exécuté directement dans l’environnement de développement et ne nécessite donc pas de méthode main d’une classe et d’un projet particulier. Le code de la méthode de test doit instancier la classe cible, appeler une de ses méthodes puis valider le comportement attendu. Normalement, dans le cadre de tests unitaires, il ne doit y avoir qu’une action sur l’objet par test.

Par exemple, pour une méthode Additionner, le test vérifie que si 2 et 3 sont passés en paramètres, alors le résultat retourné sera bien 5.

Les vérifications utilisent la classe Assertions du package org.junit.jupiter.api qui propose une collection de méthodes offrant des services beaucoup plus complets que ceux associés au mot-clé assert utilisé jusqu’alors dans cet ouvrage.

Parmi ce jeu de méthodes se trouvent la méthode assertTrue et ses surcharges.

images/09RI09V2.png

(extrait de https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html

Dans cette forme, cette méthode permet de vérifier qu’une condition est vraie et, si elle ne l’est pas, de retourner un message dans l’explorateur de tests.

Certaines méthodes de la classe Assertions proposent une version avec message et une version sans message. Il est conseillé d’utiliser...

Traitements de préparation et de nettoyage

L’exécution des tests peut être précédée par des traitements d’initialisation et suivie par des traitements de "nettoyage". Ces traitements facultatifs sont écrits dans des méthodes décorées d’attributs spéciaux qui définissent à quel moment elles seront exécutées pendant le script.

Attribut de la méthode en JUnit5

Quand sera exécutée la méthode

@BeforeAll

Une fois au tout début de la série de tests de la classe.

@BeforeEach

Avant chaque test.

@AfterEach

Après chaque test.

@AfterAll

Une fois à la fin de l’exécution de la série de tests de la classe.

Extrait de code montrant la syntaxe de toutes les méthodes d’initialisation

package demo.tests; 
 
import org.junit.jupiter.api.*; 
 
public class AutresTests { 
 
    public AutresTests(){ 
        System.out.println("Constructeur de la classe de tests"); 
    } 
 
    @BeforeAll 
    public static void avantTout(){ 
        System.out.println("Avant de lancer les tests (...)"); 
    } 
 
    @AfterAll 
    public static void apresTout(){ ...

Les tests avec paramètres externes

Imaginons que nous ayons à tester le comportement d’une méthode dans des centaines de cas d’utilisation. Écrire et maintenir la collection d’informations dans le code sera difficile et tout changement nécessitera la recompilation des tests : pas pratique ! Externaliser ses listes dans des fichiers puis effectuer leurs chargements et leurs itérations dans le test lui-même est envisageable, mais est-ce le rôle du test ? S’appuyer sur l’environnement de développement pour alimenter nos tests en données est, de loin, la solution la plus confortable et c’est ce que nous propose JUnit 5 ! On parle alors de "data-driven unit tests"...

La démonstration qui suit utilise une collection au format CSV (Comma-Separated Values). C’est en l’occurrence un fichier de format texte très simple où chaque ligne représente une collection de paramètres qui sont séparés par des virgules. On peut facilement obtenir un fichier .csv à partir d’un fichier Excel grâce à sa fonction d’exportation.

 Mettons en place notre premier "data-driven unit test" pour vérifier le fonctionnement d’une méthode retournant une chaîne de caractères. Par exemple, si on lui passe "Hello" elle doit...

Les suites de tests

Nous avons vu comment construire des classes contenant des méthodes de tests. Ces tests peuvent être exécutés depuis IntelliJ IDEA "manuellement". La manipulation peut devenir rapidement rébarbative s’il y a un nombre important de classes de tests. De plus, si nous décidions de créer une chaîne de "build" c’est-à-dire une procédure permettant de compiler et de mettre en forme le produit final, alors nous souhaiterions automatiser l’exécution des tests. C’est pour cela que nous allons nous intéresser à ce qui s’appelle une "suite de tests".

Une suite de tests est un objet qui va contenir une liste de tests à effectuer. L’objectif de cette fonctionnalité est d’automatiser des tests pour vérifier que les nouvelles fonctionnalités sont actives et qu’il n’y a pas de régression sur les anciennes. Vous allez pouvoir construire une "playlist" de tests parmi toutes les méthodes que vous avez codées.

Exercice

1. Énoncé

 Vous devrez écrire :

  • Une classe ClasseChaine contenant une méthode RetourneInitiales permettant de retourner les initiales des nom et prénom passés en paramètre sous forme d’une chaîne comme ci-dessous :

String initiales = ClasseChaine.RetourneInitiales("Andreas Dulac"); 
// initiales doit contenir "A.D." 

Si la méthode reçoit un mauvais paramètre, elle doit retourner une chaîne vide.

  • Une série de tests unitaires permettant de vérifier que tous les cas d’utilisation de la méthode ne provoquent pas de dysfonctionnement.

2. Corrigé

Les cas d’erreur sont les suivants :

  • Le paramètre étant de type String est, par nature, nullable. La valeur null passée en tant que paramètre ne doit pas provoquer de dysfonctionnement.

  • Le cas d’une chaîne vide doit être pris en compte.

  • Le cas d’une chaîne ne contenant qu’un seul mot doit également être testé.

package demo.tests; 
 
import static org.junit.jupiter.api.Assertions.*; 
 
class ClasseChaineTest { 
 
    @org.junit.jupiter.api.Test 
    void testCasNormal() { 
        String initiales = 
ClasseChaine.RetourneInitiales("Andreas Dulac"); 
       ...