Comprenez la démarche du développement piloté par les tests
Après ce premier chapitre théorique, vous en savez un peu plus sur les tests unitaires. Passons dès à présent à la pratique et écrivons notre premier test !
Mais comment procéder ?
Pour vous concentrer sur l'essentiel, la démarche est la suivante :
Savoir ce qu'on teste.
Installer le framework de tests.
Coder son test !
Commençons par décortiquer les deux premières étapes.
Identifiez le système à tester
Avant de commencer, posez-vous 5 minutes et identifiez ce que vous allez tester. Il s'agit de votre application. Plus précisément, on s'intéresse d'abord au test unitaire. Donc nous devons identifier la fonctionnalité à tester, qui sera implémentée en Java sous la forme d'une ou plusieurs classes.
C'est le système à tester ou SUT (system under test) en anglais. Le système à tester donne des résultats (ou plus généralement des sortants) à partir de données ou de paramètres de test (ou plus généralement des entrants). Puis, en vérifiant les résultats, le test est déclaré en succès ou en échec.
De manière plus formelle, il existe une manière standard de structurer un test. C'est la méthode AAA, ou Arrange - Act - Assert :
Arrange (organiser) : initialisez tous les entrants nécessaires et le système à tester si besoin.
Act (agir) : exécutez le système à tester avec les entrants précédemment initialisés dans des sortants que vous conservez.
Assert (vérifier) : validez les sortants en fonction de ce qui est attendu par rapport à vos entrants. Vous en concluez alors si c'est en succès ou en échec.
Le figure ci-dessous illustre toute la cinématique globale du test.
Ici, vous voyez que l'on se focalise sur le système à tester et qu'on ne perd pas de temps sur la manière dont les tests vont être exécutés par vos outils. En effet, c'est le rôle du framework de tests, que vous allez installer, et qui s'occupera de tout cet aspect automatiquement ensuite. Dans ce cours, nous allons utiliser JUnit.
Je vais vous montrer comment écrire des tests JUnit en développant un calculateur très basique. Nous allons créer une classe capable d’additionner deux nombres. Mais notre première étape sera d’écrire nos tests JUnit ! Surprenant ? C'est tout l'esprit de la démarche TDD (Test-driven development), qui est le fil rouge pour ce cours. Mais qu'est-ce que le TDD ?
Utilisez le TDD : rouge-vert-refactor !
Le TDD, pour test-driven development en anglais, ou développement piloté par les tests, consiste à ce que le code de votre application suive un plan fixé par les tests. En réalité, on a plutôt tendance à faire l'inverse, coder l'application, puis la tester de manière manuelle ou automatique.
Mais en codant d'abord le test, vous vous demandez directement quel objectif doit accomplir le code de votre application. Vous allez coder ce qui est nécessaire, pas plus, et ce code répondra au besoin exprimé clairement par le test.
Kent Beck, l'inventeur du TDD, a fait découvrir à de nombreux développeurs le modèle suivant, appelé red-green-refactor :
Dans ce modèle, vous répétez cycliquement les étapes suivantes :
Écrivez un test unitaire qui échoue. 🔴
Écrivez le code qui permet de réussir le test. ✅
Nettoyez le code tout en gardant les tests en succès. 🔶
Écrivez le prochain test et recommencez ! 🔄
Les tests qui échouent sont décrits comme rouges. Comme dans les feux de circulation, le rouge vous dit de vous arrêter et de faire fonctionner votre code.🚦
Quand le test est réussi, on passe au vert. Le vert vous dit de faire du refactoring. Cela signifie simplement que vous essayez de rendre votre code plus lisible et/ou plus élégant sans changer son comportement.
Étant donné que le test est déjà en place, il vous dira immédiatement si vous cassez le moindre comportement, garantissant que vous êtes toujours concentré sur la fonctionnalité à tester en priorité.
La méthode du TDD présente un autre avantage. En effet, si vous écrivez votre code après votre test, ce code est plus facile à tester. Eh oui, ce code a été fait pour être testé et est généralement plus clair ! En effet, de la même façon que vous écrivez un test dédié à chaque classe/fonction, le TDD vous encourage à développer des produits à partir de nombreuses petites classes, chacune faisant une chose et la faisant bien. Cela ajoute à la clarté du code. Il est plus facile de suivre un élément que cinquante !
À mesure que vous codez les tests, vous engrangez également des connaissances sur le code que vous devez développer pour réussir ces tests. Cela vous oriente vers une conception plus modulaire, c’est-à-dire que votre code n’est pas concentré dans seulement quelques classes. Le code modulaire est flexible et plus facile à modifier. Le TDD vous facilite la tâche.
Codez votre premier test unitaire JUnit
Entrons dans le vif du sujet ! Avant de pouvoir tester, il nous faut un projet Java. Créons-en un ! Si vous ne l’avez pas déjà fait, il vous faut une JDK, l'outil de build (construction à partir de sources) Maven, et un IDE (environnement de développement), pour vous faciliter le travail. Si besoin, vous pouvez suivre le cours Installez votre environnement de développement Java avec Eclipse.
Alors, lançons-nous dans le screencast qui suit.
Les quatre parties ci-dessous reprennent les étapes principales du screencast pour créer un projet Maven, importer JUnit, créer puis exécuter votre premier test unitaire.
Créez votre projet Maven et importez JUnit
Dans Eclipse, créez votre projet Maven. Utilisez comme indiqué dans le screencast la procédure à suivre. Pour aller plus loin dans la compréhension de Maven, vous pouvez suivre le cours Organisez et packagez une application Java avec Apache Maven
Organisez et packagez une application Java avec Apache Maven
Maven a besoin d'identifier votre projet. Pour cela, il faut définir un groupId selon le même format que les paquetages : une sorte d'URL inversé, et un artifactId sous la forme d'un identifiant. Choisissez comme groupId : com.openclassrooms.testing et artifactId : premiertest.
Après avoir créé le projet Maven, ouvrez le fichier pom.xml, il devrait ressembler à ça :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.openclassrooms.testing</groupId>
<artifactId>premiertest</artifactId>
<version>0.0.1-SNAPSHOT</version>
</project>
Nous allons indiquer à Maven que nous construisons un projet en Java 11 et utilisons le framework JUnit 5 pour les tests. Il faut pour cela :
Indiquer certaines valeurs dans les balises properties.
Se rendre sur le site Mvn Repository pour trouver la bonne dépendance JUnit.
Déclarer une version récente d'un plugin de Maven, pour que ce dernier détecte bien les tests JUnit 5.
N'hésitez pas à revoir le screencast pour bien maîtriser ces trois étapes. Néanmoins, nous n'allons pas rentrer dans les détails de la configuration ici. Ce n'est pas important pour notre cours.
Voici à quoi doit ressembler votre fichier pom.xml, après avoir ajouté toutes les bonnes valeurs :
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.openclassrooms.testing</groupId>
<artifactId>premiertest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- Indiquer l'encodage et le projet en Java 11 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
</properties>
<!-- Déclaration de la dépendance vers JUnit -->
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Pour être compatible avec JUnit 5 -->
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
</project>
Créez votre première classe de tests
Maintenant que l'on a un projet Java bien configuré, nous allons commencer par créer une classe de tests. Cette nouvelle classe contiendra tous les tests que vous utiliserez pour vérifier votre calculateur.
OK, mais où je dois créer ma classe de tests ?
Maven structure les projets Java ainsi :
le code de l'application se trouve dans le dossier src/main/java ;
les tests seront dans src/test/java.
Nous allons utiliser un nom de classe se terminant par Test. C'est une convention qui aidera les autres développeurs avec qui vous travaillez. Nous allons créer une classe dans le dossier src/test/java nommée CalculatorTest.
Avec Eclipse, créez un nouveau fichier dans src/test/java, de type "JUnit Test Case". Puis utilisez le nom de paquetage com.openclassrooms.testing et le nom de classe CalculatorTest. Après validation, vous devriez obtenir l'écran suivant :
Alors, avez-vous réussi à créer votre premier fichier de test ?
Eclipse a créé pour une structure minimale de test. Voyons ce code source de plus près.
Structurez votre premier test unitaire
Ce fichier, généré par l'IDE, contient un squelette de test minimal :
aux lignes 3 et 5, on importe les bibliothèques JUnit ;
à la ligne 10, l'annotation @Test indique que la méthode
test()
de CalculatorTest est un test ;à la ligne 11, la méthode
fail()
fait échouer le test, en expliquant qu'il n'est pas implémenté. Cette classe n'est qu'un squelette de tests, donc cette ligne permet de rappeler au développeur qu'il pense à le coder !
Allons-y, modifions ce squelette pour en faire un vrai test. L'objectif est de vérifier si notre calculateur sait ajouter deux nombres positifs.
C'est parti :
Renommez la méthode pour un nom plus descriptif de l'objectif du test, par exemple : testAddTwoPositiveNumbers.
Organisez les entrants (Arrange) : déclarez deux entiers positifs à ajouter puis une nouvelle instance de notre classe à tester (le fameux SUT). Ensuite, déclarez une instance de Calculator. Votre IDE détecte alors un problème : votre classe Calculator n'existe pas ! Il faut donc créer la classe Calculator dans src/main/java, et votre IDE vous aide pour cela !
Vous devriez maintenant avoir une classe Calculator créée, vide, et votre classe de test devrait à présent reconnaître le mot clé Calculator.
Agissez sur la classe à tester (Act) : ensuite, appelez la méthode
add()
de Calculator avec les paramètres entrants. Vous récupérerez ainsi le résultat sortant. Comme précédemment, votre IDE va détecter un problème et va vous aider à créer automatiquement la méthodeadd()
avec la bonne signature, c'est-à-dire les bons types de paramètres et le bon type de retour !Vérifiez les sortants par des affirmations (Assert) : enfin, vérifiez que le nombre attendu par cette addition est bien le résultat de la méthode
add()
précédente. On utilise pour cela des assertions ou affirmations en français. JUnit fournit des méthodes d'assertions. Elles prennent en premier paramètre ce qui est attendu (le bon résultat), et en deuxième paramètre, votre sortant de l'étape précédente.
Votre classe de test devrait alors ressembler à cela :
class CalculatorTest {
@Test
void testAddTwoPositiveNumbers() {
// Arrange
int a = 2;
int b = 3;
Calculator calculator = new Calculator();
// Act
int somme = calculator.add(a, b);
// Assert
assertEquals(5, somme);
}
}
Et votre classe Calculator, créée automatiquement par votre IDE (ici Eclipse), devrait être comme ceci :
public class Calculator {
public int add(int a, int b) {
// TODO Auto-generated method stub
return 0;
}
}
Si l'assertion est fausse, le test est tout de suite en échec. S'il y a plusieurs assertions, toutes les assertions doivent être vraies. C'est pour cela qu'en général, il est préférable d'avoir une seule assertion par test, pour mieux cibler le test.
Exécutez votre test
Votre test est écrit, bravo ! Vous pouvez maintenant l'exécuter. Votre IDE vous permet d'exécuter votre test via le menu contextuel sur la classe de test (dans Eclipse Run As - JUnit test). Vous obtiendrez l'écran suivant :
Mais, mon test échoue. 🥺
C'est normal, vous avez créé la méthode add()
, mais vous ne l'avez pas encore implémentée dans la classe Calculator. Celle-ci renvoie donc toujours 0 ! Il reste juste à coder la méthode add()
en retournant non pas 0, mais a + b, la somme des deux arguments de la méthode :
return a + b;
Modifiez le fichier Calculator et relancez le test :
C'est plus satisfaisant, non ? C'est pratique de pouvoir lancer les tests directement dans Eclipse. Mais vous pouvez aussi exécuter votre test avec Maven en ligne de commandes.
Cela sera utile lorsque vous aurez plusieurs tests. En effet, Maven est là pour automatiser toute la construction de votre projet, et donc le lancement de tous les tests !
Avec Eclipse, il faudrait cliquer sur le projet premiertest, dans le menu contextuel puis Run As - Maven test. En ligne de commandes, ce serait :
mvn test
Dans tous les cas, vous obtiendrez le résultat textuel suivant :
Le résultat s'affiche cette fois en mode console.
Ça y est, vous avez créé et exécuté votre premier test unitaire, félicitations ! De plus, vous l'avez fait selon la méthode TDD ! Cela vous perturbe d'avoir codé le test avant la classe à tester ? C'est normal. Croyez-moi, au début cela ne paraît pas intuitif. Mais une fois l'habitude prise, vous serez au contraire séduit par cette méthode et l'aide que peut vous apporter l'IDE à construire petit à petit le squelette de votre code, à partir des tests.
Essayez par vous-même !
Essayez d’ajouter une méthode Calculator.multiply
! Pensez à écrire votre test d’abord et passez-le au rouge avant d’écrire le code ! Au début du prochain chapitre, nous partirons d'une base de code contenant la solution à cet exercice. Mais cherchez d'abord par vous-même !
En résumé
JUnit est un framework de test qui vous aide à vous concentrer sur les tests à réaliser.
Pour utiliser JUnit, vous devez ajouter une dépendance de test à votre outil de développement.
Développez des logiciels à l’aide de red-green-refactor en :
commençant par écrire les tests en décrivant ce que vous devez développer (ils sont en échec, donc rouges) ;
faisant réussir les tests ; et passez-les au vert en écrivant le code de la manière la plus directe possible ;
améliorant la lisibilité de votre code en effectuant du refactoring sans casser le test.
Maintenant que vous maîtrisez les bases, rendons nos tests plus compréhensibles et plus structurés dans le prochain chapitre !