2. • Les tests sont une partie intégrante des cycles de vie en génie logiciel. En effet,
dans la plupart des méthodologies de développement logiciel, les tests sont
effectués à différentes étapes du cycle de vie du logiciel. Ainsi, on peut avoir des
tests unitaires, d'intégration, de système et de validation, chacun ayant un
objectif spécifique dans le processus de test global.
• Dans les méthodologies de développement Agile, les tests sont effectués tout au
long du cycle de vie, souvent dès le début du développement. Les tests sont
réalisés à chaque itération du processus de développement, ce qui permet une
détection précoce des erreurs et une correction rapide.
Test unitaire
3. • Les tests unitaires sont des tests permettant de tester de toutes petites unités
d’un logiciel. Ils sont notamment dédiés à des tests de méthodes.
• Les tests unitaires doivent être lancés aussi souvent que possibles, notamment à
chaque modification du code source ; ils faut donc qu’ils soient exécutables
automatiquement. Ils sont donc écrits dans le même langage de programmation
que le logiciel.
• Comme les tests unitaires doivent être exécutés fréquemment, ils doivent être
rapides, et notamment sans interaction avec un humain.
Test unitaire
4. • Un test de non-régression est une méthode de test logiciel qui vise à s'assurer
que des modifications apportées à une application n'ont pas affecté son
fonctionnement ou ses fonctionnalités existantes. L'objectif est de garantir que
le logiciel continue à fonctionner correctement après des modifications ou des
mises à jour, sans régressions.
• Les tests de non-régression sont particulièrement importants dans les projets
de développement logiciel, car ils permettent de s'assurer que les changements
apportés à une application ne perturbent pas son fonctionnement existant.
Cela permet d'éviter des régressions qui pourraient causer des problèmes pour
les utilisateurs finaux et des coûts supplémentaires pour l'entreprise en cas de
corrections nécessaires.
Test de non-régression
5. Le Test Driven Development (TDD) est une méthode de développement logiciel dans
laquelle les tests unitaires sont écrits avant le code de l'application. Le processus se
déroule généralement en trois étapes :
• Écrire un test unitaire qui décrit une fonctionnalité ou un comportement que l'on
souhaite implémenter dans l'application.
• Exécuter le test, qui doit échouer, car le code n'a pas encore été écrit.
• Écrire le code nécessaire pour que le test passe.
• Exécuter le test pour que le test passe.
• On peut procéder à des code refractoring
Test Driven Development (TDD)
6.
7. Référence : https://junit.org
Javadoc : https://junit.org/junit5/docs/current/api/index.html
Structure de base : 3 blocs :
Junit Platform : le cœur du système ; cadre général permettant d’exécuter des tests
Junit Jupiter : la bibliothèque nécessaire à l’exécution de tests
Junit 5
Junit Vintage : pour exécuter des tests Junit 3 et Junit 4 depuis Junit 5.
Intégration dans les outils de développement
Junit 5 est maintenant pris en charge par les divers IDE
(Eclipse, Netbeans, IntelliJ Idea)
Junit 5 est pris en charge par les outils de build (Maven, Gradle)
8. JUnit 5 est une réécriture intégrale du framework ayant plusieurs
objectifs :
le support et l'utilisation des nouvelles fonctionnalités de Java 8 : par
exemple, les lambdas peuvent être utilisés dans les assertions
une nouvelle architecture reposant sur plusieurs modules
le support de différents types de tests
un mécanisme d'extension qui permet l'ouverture vers des outils tiers
ou des API
9. • Structure d’une classe de test
• 1 classe de test = un ensemble de méthodes de test
• 1 classe de test par classe à tester
• 1 méthode de test = 1 cas de test
• 1 cas de test = (description, données d’entrée, résultat attendu)
• Structure d’une méthode de test de base
• méthode d’instance publique
• annotée avec @Test
• ne prend aucun paramètre
• ne renvoie rien
• lève une AssertionError en cas de test échoué
• Conventions
• nom d’une classe de test : NomClasseTestéeTest
• nom d’une méthode de test : testNomMethodeTestee
Structure et conventions
10. public class Calcul {
public int Add(…){ … }
public int Multiply(…) { … }
public double devide() {…}
}
11. import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class CalculTest {
private Calcul calcul;
@BeforeEach
public void setUp() { calcul = new Calcul(); }
@Test
@DisplayName("Test de l'addition")
public void testAdd() {
int result = calcul.Add(2, 3);
assertEquals(5, result, "La méthode Add ne fonctionne pas comme prévu");
}
@Test
@DisplayName("Test de la multiplication")
public void testMultiply() {
int result = calcul.Multiply(2, 3);
assertEquals(6, result, "La méthode Multiply ne fonctionne pas comme prévu"); }
}
12. Ensemble de méthodes statiques aidant à écrire des tests. Ces méthodes lèvent des
AssertionFailedError (sous-type de AssertionError) en cas d’échec.
Classe Assertions
Structure des différentes méthodes
La plupart de ces méthodes existent sous 3 formats :
• avec les paramètres de base
• avec les paramètres de base et une chaîne de caractères correspondant au
message à afficher en cas d’échec
• avec les paramètres de base et un Supplier<String>, méthode construisant la
chaîne de caractères à afficher en cas d’échec
14. Le cycle de vie des tests
JUnit 5 créé une nouvelle instance pour exécuter chaque méthode de tests.
Une classe de test JUnit peut avoir des méthodes annotées pour définir des actions exécutées
durant le cycle de vie des tests. Le cycle de vie d'un test peut être enrichi grâce à quatre
annotations utilisées sur des méthodes pour réaliser des initialisations ou du ménage :
• @BeforeAll : exécutée une seule fois avant l'exécution du premier test de la classe
• @BeforeEach : exécutée avant chaque méthode de tests
• @AfterEach : exécutée après chaque méthode de tests
• @AfterAll : exécutée une seule fois après l'exécution de tous les tests de la classe
Par défaut, une nouvelle instance est créée pour exécuter chaque méthode de tests : il n'y a alors
pas d'instance à utiliser pour invoquer les méthodes @BeforeAll et @AfterAll. Celles-ci doivent
donc être statique.
15. Dans le cycle de vie des instances de tests, les méthodes de tests sont des méthodes
annotées avec @Test, @ParameterizedTest, @RepeatedTest, @TestFactory ou
@TestTemplate.
Par défaut, JUnit créé une nouvelle instance pour chaque test avant d'exécuter la méthode
concernée. Le but de ce comportement est d'exécuter le test de manière isolée et ainsi
d'éviter les effets de bord liés à l'exécution des autres tests.
Il est possible de modifier ce comportement par défaut en utilisant l'annotation
@org.junit.jupiter.api.TestInstance.
Elle ne possède qu'un seul attribut de type TestInstance.Lifecycle qui est une énumération
possédant deux valeurs :
Le cycle de vie des tests
Valeur Rôle
PER_CLASS
Une seule instance est créée pour tous les tests
d'une même classe
PER_METHOD
Une nouvelle instance est créée pour exécuter
chaque méthode de test. C'est la valeur par
défaut de l'attribut de l'annotation
@TestInstance
16. public double devide(double x, double y) throws IllegalArgumentException {
if (y == 0) {
throw new IllegalArgumentException("Cannot divide by zero.");
}
return x / y;
}
Tester une exception
17. import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class CalculTest {
private static Calcul calcul;
@BeforeAll
public static void init() {
calcul = new Calcul();
}
@Test
public void testDevide() {
double result = calcul.devide(10, 5);
assertEquals(2, result, 0.001);
}
@Test
public void testDevideByZero() {
assertThrows(IllegalArgumentException.class, () -> {calcul.devide(10, 0);
});
}
}
18. L'assertion assertAll
L'assertion assertAll permet de regrouper plusieurs assertions qui seront toutes
exécutés. L'assertion assertAll échoue si au moins une des assertions qu'elle
regroupe échoue. Même si une assertion du groupe échoue, toutes les
assertions du groupe seront évaluées.
@Test
void test() {
String s1 = "Hello";
String s2 = "World";
String s3 = "JUnit";
assertAll("Test multiple assertions",
() -> assertEquals("Hello", s1),
() -> assertEquals("World", s2),
() -> assertEquals("JUnit", s3)
);
}
19. @RepeatedTest permet d'exécuter une même méthode de test plusieurs fois avec les mêmes
arguments ou sans arguments. Cela peut être utile pour différents scénarios de test, par exemple :
• Vérifier que la méthode de test est bien stable et reproductible sur plusieurs exécutions.
• Tester la performance d'une méthode de test en mesurant le temps d'exécution ou le nombre
d'itérations qu'elle peut traiter avant de rencontrer une erreur ou une exception.
• Tester la résilience d'une méthode de test en l'exécutant plusieurs fois avec les mêmes arguments
et en vérifiant qu'elle ne provoque pas d'erreurs ou d'exceptions.
Exécuter plusieurs fois une méthode de test
@RepeatedTest(5)
@DisplayName("Repeated Test Example")
void repeatedTest(TestReporter testReporter) {
int actualResult = 5 + 10;
int expectedResult = 15;
assertEquals(expectedResult, actualResult, "The addition of 5 and 10 should equal 15");
testReporter.publishEntry("The addition of 5 and 10 equals " + actualResult);
}
20. @RepeatedTest(value = 5, name = "Repeated Test Example {currentRepetition}/{totalRepetitions}")
@DisplayName("Repeated Test Example")
@Tag("tag1")
void repeatedTest(TestInfo testInfo) {
double actualResult = 1 + 2;
double expectedResult = 3;
assertEquals(expectedResult, actualResult, "The addition of 0.1 and 0.2 should equal 0.3");
System.out.println("Test " + testInfo.getDisplayName() + " executed with tags " +
testInfo.getTags());
}
Exécuter plusieurs fois une méthode de test
21. L'annotation @Tag est utilisée pour marquer une classe ou une méthode de test avec
des tags, qui sont des étiquettes permettant de regrouper les tests en fonction de
leur objectif, de leur portée, de leur niveau de priorité, ou de tout autre critère que
vous pouvez définir.
Les tags peuvent être utilisés pour exécuter une sélection de tests spécifiques ou
pour exclure certains tests lors de l'exécution.
L'annotation @Tag
Par exemple, pour exécuter tous les tests marqués avec le tag integration, vous
pouvez utiliser la commande suivante :
mvn test -Dgroups=tag1
Si vous avez plusieurs tags, vous pouvez les séparer par une virgule :
mvn test -Dgroups=tag1,tag2
Cela exécutera tous les tests associés aux tags integration et smoke.
22. Notez que pour utiliser l'option -Dgroups, vous devez inclure le plugin Surefire
dans votre projet Maven et définir la propriété groups dans le fichier pom.xml :
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<groups>tag1,tag2</groups>
</configuration>
</plugin>
</plugins>
23. Les Tests paramétrés @ParameterizedTes
Les tests paramétrés permettent d'exécuter un même test plusieurs fois avec
différentes valeurs qui lui sont passés en paramètres.
La méthode de test doit être annotée @ParameterizedTest. Il est aussi nécessaire de
déclarer une source de données permettant d'obtenir les différentes valeurs à
l'exécution des tests.
24. Annotation Rôle
@ValueSource
Une source de données simple sous la forme
d'un tableau de chaînes de caractères ou de
primitifs (int, long ou double)
@EnumSource
Une source de données simple sous la forme
d'une énumération
@MethodSource
Une source de données dont les valeurs sont
fournies par une méthode
@CsvSource
Une source de données dont les valeurs sont
fournies sous la forme de chaînes de caractères
dans laquelle chaque argument est séparé par
une virgule
@CsvSourceFile
Une source de données dont les valeurs sont
fournies sous la forme d'un ou plusieurs fichiers
CSV
@ArgumentsSource
Une source de données qui est une méthode
d'une instance de type ArgumentProvider
26. @DisplayName("Addition")
@ParameterizedTest()
@CsvSource({ "1, 1", "1, 2", "2, 3" })
void testAdditioner(int a, int b) {
int attendu = a + b;
assertEquals(attendu, a + b);
}
@DisplayName("Addition")
@ParameterizedTest()
@CsvFileSource(resources = "additionner_source.csv")
void testAdditionner(int a, int b) {
int attendu = a + b;
assertEquals(attendu, a + b);
}
27. Les tests imbriqués
Les tests imbriqués permettent de grouper des cas de test pour renforcer le lien qui
existent entre-eux.
JUnit 5 permet de créer des tests imbriquées (nested tests) en utilisant une
annotation @Nested sur une classe interne. Seules les classes internes non statiques
peuvent être annotées avec @Nested.
29. Si un test échoue dans la classe mère qui contient des classes de tests imbriquées,
JUnit ne teste pas automatiquement les classes de tests imbriquées. Les tests
imbriqués ne sont pas exécutés indépendamment des tests de la classe mère.
mvn test -Dtest= MyClassTest#MonTestImbrique