Prise en main
Quelques liens utiles :Site officiel Junit 5
User guide
API documentation
Les tests sont organisés en utilsant des annotations (voir la liste complète).
On va utiliser une classe d'exemple pour vérifier que Junit5 est opérationnel :
import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class FirstJUnit5Tests { @Test void myFirstTest() { assertEquals(2, 1 + 1); } }(code dans exemples/java/junit5/first)
En mode console
On peut exécuter JUnit5 en ligne de commande en utilisantjunit-platform-console-standalone
(version utilisée : 1.8.2).
Télécharger junit-platform-console-standalone-1.8.2.jar et le stocker en local.
(une copie se trouve sur le répertoire bin/ de ce cours - accessible depuis la page d'accueil du cours).
Pour avoir la liste des options à passer à junit :
java -jar /path/to/junit-platform-console-standalone-1.8.2.jar -h # ou plus simplement java -jar /path/to/junit-platform-console-standalone-1.8.2.jar
Compiler / exécuter
En se plaçant dans le répertoire contenantFirstJUnit5Tests.java
:
Pour compiler :
javac -cp /path/to/junit-platform-console-standalone-1.8.2.jar FirstJUnit5Tests.javaPour exécuter la classe de test :
java -jar /path/to/bin/junit-platform-console-standalone-1.8.2.jar -cp . -c FirstJUnit5Tests(noter que les options
-cp
et -c
sont ici des options passées à JUnit, pas des options de java).
Avec Eclipse
- Créer un nouveau projet java.
-
Dans
src/
, clic droit / new JUnit test case ; nommez-leFirstJUnit5Tests
.
Conservez l'action "Add Junit 5 library to the build path". -
Recopiez le contenu de
FirstJUnit5Tests
(La ligneimport org.junit.jupiter.api.Test;
est inutile). - Clic droit sur le fichier
FirstJUnit5Tests.java
/ Run as Junit test.
Tester une classe
Par convention, les tests unitaires sont situés dans des classes dont le nom finit parTest
(par exemple, les tests de la classe MyClass
seront dans MyClassTest
).
Par convention, les méthodes de test commencent par
test
, par exemple testMyMethod()
.
Les méthodes de test sont annnotées avec
@Test
; elles vont être exécutées par JUnit et leur résultat va être inclus dans le rapport.
Assertions
Ces méthodes contiennent des assertions qui doivent être vérifiées pour que le test soit valide.Ces assertions sont exprimées avec des méthodes commençant par
assert
de la classe org.junit.jupiter.api.Assertions
.
Autres annotations courantes :
-
@BeforeAll
et@AfterAll
servent à identifier des méthodes qui seront exécutées une seule fois avant et après tous les tests ; utile par exemple pour initialiser une connection réseau ou à une base de données. -
@BeforeEach
et@AfterEach
identifient des méthodes qui seront exécutées avant / après chaque test. Servent par exemple à initialiser des structures de données (fixtures) et garantir que chaque méthode de test disposera d'un état identique. Par exemple, si on veut tester l'implémentation d'une liste, on peut créer une liste vide, qui sera utilisable par toutes les méthodes de test.
assertEquals()
avec des float
ou des double
, utilisez les méthodes avec un paramètre delta
, par exemple :
assertEquals(float expected, float actual, float delta)qui signifie "assert presque égal" (égalité stricte impossible due à la représentation binaire des nombres réels).
Exemple
Liste deString
contenant ces méthodes publiques :
public class CustomList{ /** Renvoie le nombre d'élément dans la liste **/ public int getLength(){ ... } /** Ajoute un élément à la fin de la liste **/ public void add(String s){ ... } /** Supprime un élément de la liste **/ public void remove(int index){ ... } /** Renvoie un élément de la liste **/ public String getElement(int index){ ... } }On pourrait avoir la classe de test :
import org.junit.jupiter.api.*; // pour les annotations import org.junit.jupiter.api.Assertions; // pour les méthodes assert*() public class CustomListTest{ private CustomList l1; @BeforeAll static void setup() { // exécutée une fois avant tous les tests } @BeforeEach void init() { l1 = new CustomList(); } @AfterEach void tearDown() { // exécutée après chaque méthode marquée @Test } @AfterAll static void done() { // exécutée une fois après tous les tests } // // Méthodes de tests unitaires // @Test void testListeVide(){ assertEquals(l1.getLength(), 0); } @Test void testAdd(){ String str = "test"; l1.add(str); assertEquals(l1.getLength(), 1); assertEquals(l1.get(0), str); } }
Tester les exceptions
Il existe des méthodesorg.junit.jupiter.api.Assertions.assertThrows
, mais demande d'avoir vu les lambda expressions.
On peut cependant tester qu'une exception a bien été lancée en utilisant
org.junit.jupiter.api.Assertions.fail()
Par exemple, l'implémentation de
CustomList.remove()
doit renvoyer une java.lang.IndexOutOfBoundsException
si on essaye de supprimer un élément inexistant.
Dans
CustomListTest
, on pourrait avoir une méthode
@Test void testRemove(){ try{ l1.remove(0); // une IndexOutOfBoundsException devrait être lancée par la ligne précédente // donc si fail() est exécuté, c'est que remove() ne s'est pas comporté comme attendu. fail("Opération remove() effectuée sur un index inexistant"); } catch(IndexOutOfBoundsException e){ // test réussi } }Ou alors :
@Test void testRemove(){ try{ l1.remove(0); // une IndexOutOfBoundsException devrait être lancée par la ligne précédente // donc si fail() est exécuté, c'est que remove() ne s'est pas comporté comme attendu. fail("Opération remove() effectuée sur un index inexistant"); } catch(Exception e){ assertTrue(e instanceof IndexOutOfBoundsException); } }
Test Suite : tester plusieurs classes
En plus de junit-platform-console-standalone-1.8.2.jar, les jars suivants doivent être dans le classpath (à la fois pour compiler et pour exécuter) :- junit-platform-suite-api-1.8.2.jar
- junit-platform-suite-commons-1.8.2.jar
- junit-platform-suite-engine-1.8.2.jar
Exemple : suite de tests pour le TP2 : conversion
package conversion; import conversion.model.DegresTest; import conversion.utils.FormatTest; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; import org.junit.platform.suite.api.SelectClasses; @Suite @SuiteDisplayName("TP2 conversion test suite") @SelectClasses({DegresTest.class, FormatTest.class}) public class ConversionTestSuite {}On signale à JUnit une suite de tests en utilisant
@Suite
.
Dans cet exemple, on a utilisé
@SelectClasses
pour indiquer quelles classes de tests unitaires exécuter.
D'autres annotations permettent de spécifier les classes de test (
@SelectPackages
etc.), voir par exemple
JUnit5 User Guide
ou
https://howtodoinjava.com/junit5/junit5-test-suites-examples
Compilation
test-suite-compile
#!/bin/sh dir_bin='../../bin/tps/2-conversion' dir_jar='../../../bin' jar1_junit="$dir_jar/junit-platform-console-standalone-1.8.2.jar" jar2_junit="$dir_jar/junit-platform-suite-api-1.8.2.jar" jar3_junit="$dir_jar/junit-platform-suite-commons-1.8.2.jar" jar4_junit="$dir_jar/junit-platform-suite-engine-1.8.2.jar" # compile chacun des tests ./test-compile # compile la suite de tests command="javac -d $dir_bin -cp $jar1_junit:$jar2_junit:$jar3_junit:$jar4_junit:$dir_bin tests/conversion/ConversionTestSuite.java" echo $command $commandAvant de compiler la suite, chaque test doit être compilé :
test-compile
#!/bin/sh # Cette commande peut être exécutée directement # ou être exécutée depuis test-suite-compile dir_bin='bin' jar_junit='../../../bin/junit-platform-console-standalone-1.8.2.jar' command="javac -d $dir_bin -cp $jar_junit:$dir_bin tests/conversion/utils/FormatTest.java" echo $command $command command="javac -d $dir_bin -cp $jar_junit:$dir_bin tests/conversion/model/DegresTest.java" echo $command $command
Exécution
test-suite-run
#!/bin/sh dir_bin='../../bin/tps/2-conversion' dir_jar='../../../bin' jar1_junit="$dir_jar/junit-platform-console-standalone-1.8.2.jar" jar2_junit="$dir_jar/junit-platform-suite-api-1.8.2.jar" jar3_junit="$dir_jar/junit-platform-suite-commons-1.8.2.jar" jar4_junit="$dir_jar/junit-platform-suite-engine-1.8.2.jar" command="java -jar $jar1_junit -cp $dir_bin:$jar2_junit:$jar3_junit:$jar4_junit -c conversion.ConversionTestSuite" echo $command $command
Conseils
Place des tests
Il est conseillé de séparer le code de test du code de production (car le code de test n'a pas besoin d'être déployé en production).Mais on a souvent besoin de mettre le code de test dans le même package que le code de production car cela permet d'avoir accès dans les tests aux classes et méthodes ayant une visibilité
package
.
C'est possible en utilisant les options classpath (
-cp
) et destination (-d
) de javac
et java
.
Voir exemples/java/junit5/maven-dirs, qui reproduit la hiérarchie par défaut de maven :
maven-dirs ├── bin │ └── project1 │ ├── Project1.class │ └── Project1Test.class └── src ├── main │ └── java │ └── project1 │ └── Project1.java └── test └── java └── project1 └── Project1Test.javaOn compile d'abord le code à tester dans
bin
Puis on compile le code de test en incluant
bin
dans le classpath :
javac -d bin src/main/java/project1/Project1.java javac -cp /path/to/junit-platform-console-standalone-1.8.2.jar:bin -d bin src/test/java/project1/Project1Test.java