Quand vous cherchez quelque chose dans un livre, vous pouvez généralement vérifier la table des matières et passer directement au bon chapitre. 📖 Si vous devez parcourir chaque page pour trouver ce que vous cherchez, soit le sommaire est mauvais, soit il est mal structuré. Le même constat s’applique aux tests. Voyons quelques façons de rendre vos tests lisibles.
Si vous souhaitez vous exercer vous-même sur les exemples abordés ci-dessous appliqués au calculateur, vous pouvez partir d'une base de code avec la branche de ce chapitre p2ch1, à partir du même dépôt de code de cours (pour rappel, il s'agit de https://github.com/geoffreyarthaud/oc-testing-java-cours) :
git checkout -f p2ch1
Catégorisez vos tests JUnit 5 avec @Tag
Avec JUnit 5, catégoriser des tests est devenu plus facile. Vous pouvez utiliser en particulier l'annotation @Tag de JUnit5. Elle vient remplacer l’utilisation de @Category de JUnit 4, peu pratique car vous deviez créer une interface vide à chaque fois.
Voici un exemple d'utilisation des tags pour le calculateur :
@Test
@Tag("QuatreOperations") // ce test fait partie des tests des 4 opérations de base
public void testAddTwoPositiveNumbers() {
// Arrange
int a = 2;
int b = 3;
// Act
int somme = calculatorUnderTest.add(a, b);
// Assert
assertThat(somme).isEqualTo(5);
assertEquals(5, somme);
}
@Test
@Tag("QuatreOperations") // ce test fait partie des tests des 4 opérations de base
public void multiply_shouldReturnTheProduct_ofTwoIntegers() {
// Arrange
int a = 42;
int b = 11;
// Act
int produit = calculatorUnderTest.multiply(a, b);
// Assert
assertEquals(462, produit);
}
Comme pour des articles web possédant plusieurs tags, un test peut posséder plusieurs tags. Et pour ne pas se répéter, on peut aussi placer l'annotation @Tag au niveau d'une classe, ce qui permet d'ajouter ce tag à tous les tests de cette classe !
En complément de l’annotation @Tag, voici deux autres annotations très pratiques : @DisplayName et @Nested. Regardons les points les plus intéressants de nos 3 annotations étape par étape.
Voici un code plus complet contenant ces trois annotations :
@Tag("ConversionTests") // (1)
@DisplayName("Réussir à convertir entre différentes unités.") // (2)
public class ConversionCalculatorTest {
private ConversionCalculator calculatorUnderTest = new ConversionCalculator();
@Nested // (3)
@Tag("TemperatureTests") // (4)
@DisplayName("Réussir à convertir des températures") // (4)
class TemperatureTests {
@Test
@DisplayName("Soit une T° à 0°C, lorsque l'on convertit en °F, alors on obtient 32°F.")
public void celsiusToFahrenheit_returnsAFahrenheitTempurature_whenCelsiusIsZero() {
Double actualFahrenheit = calculatorUnderTest.celsiusToFahrenheit(0.0);
assertThat(actualFahrenheit).isCloseTo(32.0, withinPercentage(0.01));
}
@Test
@DisplayName("Soit une T° à 32°F, lorsque l'on convertit en °C, alors on obtient 0°C.")
public void fahrenheitToCelsius_returnsZeroCelciusTempurature_whenThirtyTwo() {
Double actualCelsius = calculatorUnderTest.fahrenheitToCelsius(32.0);
assertThat(actualCelsius).isCloseTo(0.0, withinPercentage(0.01));
}
}
@Test
@DisplayName("Soit un volume de 3.78541 litres, en gallons, on obtient 1 gallon.")
public void litresToGallons_returnsOneGallon_whenConvertingTheEquivalentLitres() {
Double actualLitres = calculatorUnderTest.litresToGallons(3.78541);
assertThat(actualLitres).isCloseTo(1.0, withinPercentage(0.01));
}
@Test
@DisplayName("L'aire d'un disque de rayon 1 doit valoir PI.")
public void radiusToAreaOfCircle_returnsPi_whenWeHaveARadiusOfOne() {
Double actualArea = calculatorUnderTest.radiusToAreaOfCircle(1.0);
assertThat(actualArea).isCloseTo(PI, withinPercentage(0.01));
}
}
(1) Ligne 1 : @Tag désigne tous les tests de la classe comme étant des tests de conversion, avec un tag nommé "ConversionTests".
(2) Ligne 2 : @DisplayName vous permet de nommer vos tests de façon lisible par tous.
(3) Ligne 7 : @Nested vous permet de grouper vos tests dans une classe interne. Avec @Nested, si un seul test échoue, tout le groupe désigné par cette annotation échoue !
(4) Ligne 8-9 : vous pouvez ajouter @Displayname et @Tag à chaque bloc @Test et @Nested.
Grâce à ces annotations, quand le test JUnit5 est exécuté, les résultats sont également lisibles plus naturellement :
Utilisez @ExtendWith pour modifier l'exécution des tests
@ExtendWith vous permet de modifier le déroulement des tests menés par JUnit 5.
Attendez, je croyais que je devais me concentrer sur le système à tester, et que JUnit s'occupait du reste !
Tout à fait ! Oui, JUnit s'occupe de tout comme on l'a vu ; nous avions besoin uniquement de @Test pour désigner ces méthodes comme tests, et les annotations @BeforeEach, @AfterEach, etc., permettaient de regrouper des traitements communs !
Mais il existe des situations où vous aurez besoin de personnaliser le déroulement des tests ou la gestion des classes de tests. Voici un exemple : imaginez un cas où on aurait besoin d'une extension qui permette à notre classe de test d'utiliser une classe de log (un journal enregistrant les messages à afficher) pour afficher les messages, au lieu d'utiliser println
.
Ajoutez la dépendance suivante dans votre fichier pom.xml (vous pouvez choisir une version plus récente si vous le souhaitez, en consultant mvnrepository.com) :
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
Créez la classe LoggingExtension dans le même paquetage que CalculatorTest avec le contenu suivant :
package com.openclassrooms.testing;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
public class LoggingExtension implements TestInstancePostProcessor {
@Override
public void postProcessTestInstance(Object testInstance,
ExtensionContext context) throws Exception {
Logger logger = LogManager.getLogger(testInstance.getClass());
testInstance.getClass()
.getMethod("setLogger", Logger.class)
.invoke(testInstance, logger);
}
}
Nous n'allons pas détailler le mécanisme de cette classe. Sachez simplement que cette extension implémente une interface permettant de manipuler la classe de test, juste après sa création.
Puis utilisez cette extension dans la classe de test CalculatorTest. Nous ajoutons l'annotation @ExtendWith, puis nous ajoutons une instance de classe Logger et une méthode setLogger()
associée. Ensuite, on remplace tous les println
par logger.info
:
@ExtendWith(LoggingExtension.class)
public class CalculatorTest {
private static Instant startedAt;
private Calculator calculatorUnderTest;
private Logger logger;
public void setLogger(Logger logger) {
this.logger = logger;
}
@BeforeEach
public void initCalculator() {
logger.info("Appel avant chaque test");
calculatorUnderTest = new Calculator();
}
@AfterEach
public void undefCalculator() {
logger.info("Appel après chaque test");
calculatorUnderTest = null;
}
...
Puis créez un fichier log4j2.properties dans le dossier src/test/resources avec le contenu suivant :
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n
rootLogger.level = info
rootLogger.appenderRefs = stdout
rootLogger.appenderRef.stdout.ref = STDOUT
Ce fichier indique tout ce que le composant de log a besoin de savoir pour bien formater les messages.
Et grâce à l'extension, vos classes de tests sont munies d'un logger !
Découvrez @Disabled
Vous êtes-vous déjà retrouvé dans une situation où vous travaillez dur sur un projet, mais la personne à côté de vous n’arrête pas de parler ? Et vous n’arrivez à rien faire parce que vous êtes tellement distrait ? Vous vous êtes peut-être senti coupable, mais je suis sûr que vous avez souhaité disposer d’un bouton "muet".
J’ai connu des tests exactement comme ça ! Certains qui avaient échoué et qui me donnaient des lignes et des lignes de texte en rouge, dont je ne savais pas du tout quoi faire. Évidemment, il vaut mieux corriger le test si vous le pouvez. Néanmoins, même si cela doit rester temporaire, si le test a été mal écrit ou que vous ne comprenez même son objectif, dites-lui simplement de se taire ! Vous pouvez le faire en utilisant le @Disabled de JUnit.
Par exemple, ajouter l’annotation suivante avant votre @Test empêcherait le test de s’exécuter et expliquerait pourquoi ! Par exemple :
@Disabled("Stoppé car cela échoue tous les mardis")
Assurez-vous de spécifier pourquoi vous ignorez le test et promettez de revenir le corriger plus tard !
En résumé
Nous pouvons classer les tests en catégories en utilisant @Tag de JUnit5.
@ExtendWith peut être utilisé pour modifier le déroulement des tests.
@Disabled peut être utilisé pour faire taire temporairement les tests inutiles ou problématiques.
Retrouvez le code de ce chapitre intégré en choisissant la branche p2ch2 du dépôt de code.
Cette fois, nous avons fait le tour des annotations JUnit 5 les plus importantes. Il est temps d'aborder un sujet nouveau et central de ce chapitre, à savoir les bonnes pratiques à suivre pour savoir si vos tests sont bien unitaires et efficaces !