Il y a 7 ans -
Temps de lecture 8 minutes
JUnit 5 rencontre lambda
JUnit est un framework conçu pour faire des tests unitaires en respectant l’architecture xUnit. Il a accompagné le Test Driven Development (TDD) depuis le début des années 2000 et a inspiré la création d’autres alternatives comme TestNG.
Il n’existe pas vraiment d’outil standard pour faire des tests dans Java, mais JUnit est le plus répandu.
JUnit 4 est sorti en 2005, lors de l’introduction des annotations dans le langage Java. La version 8 de Java est la mise à jour la plus importante depuis la version 5 car elle introduit certains concepts de programmation fonctionnelle, notamment les lambdas. Un appel à crowdfunding a été lancé mi-2015 pour trouver le budget, afin de financer le développement d’une version qui supporte les lambda dans JUnit. Depuis février 2016, on peut trouver la première version 5 alpha avec quelques exemples d’utilisation et il est déjà possible d’utiliser la première version milestone.
Dans cet article, nous allons découvrir les nouvelles fonctionnalités de JUnit 5 avec un code d’exemple construit avec Gradle .
Support
JUnit 5 n’est, pour le moment, intégré avec aucun IDE; néanmoins, il est possible de l’utiliser avec :
-
le runner pour Maven Surefire
-
le plugin pour Gradle
Il n’y a pas encore support « officiel » de JUnit 5 sur Gradle comme celui qu’on peut trouver pour JUnit4 et TestNG. Cependant, il est possible d’utiliser le plugin embarqué dans la release.
La configuration JUnit d’un projet Gradle doit comprendre le plugin org.junit.gen5.gradle :
[groovy]apply plugin: ‘org.junit.gen5.gradle'[/groovy]
et les bibliothèques de JUnit5 :
[groovy]dependencies {
classpath ‘org.junit:junit-gradle:5.0.0-SNAPSHOT’
}[/groovy]
Rétrocompatibilité
Il est possible d’utiliser JUnit 5 et JUnit 4 ensemble avec le flag
et en déclarant les bibliothèques de JUnit 4 comme dépendances.
runJunit4=true
[groovy]ext.junit4Version = ‘4.12’ //quelle version de JUnit 4 je veux
junit5 {
version ‘5.0.0-SNAPSHOT’
runJunit4 true // je veux executer des tests JUnit4
matchClassName ‘.*Test’
reportsDir file(‘build/test-results/junit5/’) // par defaut
logManager ‘org.apache.logging.log4j.jul.LogManager’
requireTag ‘firstTest’
}
dependencies {
// Pour les tests JUnit 4
testCompile("junit:junit:${junit4Version}")
testRuntime("org.apache.logging.log4j:log4j-core:${log4JVersion}")
testRuntime("org.apache.logging.log4j:log4j-jul:${log4JVersion}")
compile "org.mockito:mockito-core:2.+"
}
task wrapper(type: Wrapper) {
distributionUrl=’https://services.gradle.org/distributions/gradle-2.9-all.zip’
}
[/groovy]
Pour le moment, il est possible d’exécuter les tests en utilisant soit
[groovy]gradle junit5Test[/groovy]
soit
[groovy]gradle check[/groovy]
L’option debug
de la task junit5Test ( --debug-jvm
) est en cours de développement. Le processus démarre suspendu (en attente de connexion) et écoute sur le port 5005. Avec cette implémentation, il sera possible de brancher le debug aux applications externes.
Le Code d’exemple
L’exemple sur lequel on va travailler consiste en une simple liste de tâches, implémenté en Java 8, et est téléchargeable sur mon github (@dicaormu). Il contient une classe
avec un nom, une priorité et une date de fin :
Task
[java]class Task {
private String name;
private Priority priority;
private LocalDate date;
…
}[/java]
La classe
contient une liste de tâches et des méthodes pour gérer cette liste. Il est donc possible de compléter des tâches avec une priorité donnée et de retourner le temps d’attente d’une Task.
TodoListService
[java]public class TodoListService {
public void completeListByPriority(List<Task> tasks, Task.Priority priority) {
// code qui complete une liste de Tasks
for(Task task:tasks){
if(task.getPriority().equals(priority)) {
task.setPriority(Task.Priority.COMPLETE);
}
}
}
public double getTaskDelayInMonths(Task task) {
if (task.getDate().isBefore(LocalDate.now()))
throw new IllegalArgumentException("Not a valid task");
Period period = Period.between(LocalDate.now(), task.getDate());
return (Double.valueOf(period.getDays()) / 30) + period.toTotalMonths();
}
…
}[/java]
Écrire un test avec JUnit5
Les classes pour l’exécution de JUnit test se trouvent sous le package
. Dans le fichier gradle, on a défini le pattern pour les tests: .
org.junit.gen5.api
*Test .
Il est désormais possible de mettre un Tag dans les tests avec l’annotation
[java]@Tag("TAG")[/java]
qui sert à filtrer les tests a posteriori.
Il est possible aussi d’ajouter un message en cas d’erreur avec l’annotation
[java]@DisplayName("Test Name")[/java]
Dans ce cas,
sert à identifier le test et il sera affiché dans le log si le test échoue.
"Test Name"
A l’intérieur de la classe, chaque test est annoté avec
comme on est habitué avec JUnit 4.
@Test,
[java]@Test
@DisplayName("First case: when changing my list and getting collection of one item")
public void
should_change_an_item_and_get_one_item_list() {
List<Task> resp = todoListService.completeListByPriority(todoList, Task.Priority.NEW);
assertNotNull(resp, errorsMessage);
assertTrue(resp.size() == 1);
assertTrue(resp.contains(new Task("task1", Task.Priority.COMPLETE)));
}
[/java]
Il est également possible d’ignorer un test en utilisant l’annotation @Disabled
au lieu de @Ignore
comme ce fut le cas dans JUnit 4.
[java]@Disabled
@Test
@DisplayName("second case: when changing my list and getting completed collection. Not implemented yet")
public void
should_change_an_item_and_get_completed_list() {
…
}[/java]
Assertions
La mise à jour
On peut trouver les assertions suivantes dans JUnit :
Equality | Nullability | Exceptions |
---|---|---|
assertEquals assertFalse assertNotEquals assertSame assertTrue |
assertNotNull assertNotSame assertNull |
assertThrows expectThrows
|
La plupart de ces assertions existent dans JUnit 4; cependant, elles ont toutes été réécrites. Afin de différencier les versions successives d’assertions, le message de l’assertion dans JUnit 5 peut être passé comme dernier paramètre, quand il existe. Ce message peut être aussi défini comme un
, c’est-à-dire :
Supplier<String>
[java]assertNotNull(resp, () -> "Test Failed, asserting not null");[/java]
ou
[java]assertTrue(resp.size() == 3, "Collection size must be the same");[/java]
AssertAll
Une autre nouvelle caractéristique est la possibilité de grouper les erreurs de plusieurs assertions avec
. Par exemple :
assertAll
[java]assertAll("Assertions in the collection",
() -> assertTrue(resp.contains(new Task("task1", Task.Priority.COMPLETE))),
() -> assertTrue(resp.contains(new Task("task2", Task.Priority.PROGRESS))),
() -> assertTrue(resp.contains(new Task("task3", Task.Priority.COMPLETE)))
);[/java]
Avec
toutes les assertions sont exécutées et les erreurs sont rapportées en un seul bloc.
assertAll
AssertThrows
Une autre assertion très utile est
assertThrows
.
On peut constater qu’avec l’utilisation d’
, il est plus simple de vérifier si le code lance une exception. Il n’ est alors plus nécessaire d’écrire une
assertThrows
au début du test.
@Rule
[java]@Test
@DisplayName("fourth case: getting task delay if possible")
public void
should_get_delay_exception_when_task_date_under_today() {
assertThrows(IllegalArgumentException.class,
() -> todoListService.getTaskDelayInDays(new Task("task1", Task.Priority.NEW, LocalDate.now().minusDays(1))));
}[/java]
Interfaces avec des méthodes default et tests imbriqués
Nouveauté: il est possible de créer des tests comme méthodes default dans une interface java. C’est-à-dire:
[java]public interface IntrTodoListServiceTest {
Task add1MonthToTask(Task newTask);
@Test //default test method
default void
should_add_tasks_in_date_when_date_is_null() {
Task task2add = new Task("task1", Task.Priority.NEW, LocalDate.of(2016, 3, 1));
Task t = add1MonthToTask(task2add);
assertEquals(t.getDate(), LocalDate.of(2016, 3, 31));
}
}[/java]
Évidement, il est nécessaire d’implémenter l’interface pour faire passer les test.
@Nested
JUnit 5 donne aussi l’opportunité de créer des tests imbriqués, afin de regrouper un ensemble de tests reliés dans une même classe. Dans ce but, l’annotation @Nested est utilisée. Par exemple:
[java]@Tag("firstTest")
@DisplayName("Testing Todo List Service")
public class TodoListServiceTest {
@Test
@DisplayName("First case: when changing my list")
public void
should_change_an_item() {
//implementation
}
….
@Nested
@DisplayName("When changing tasks")
class TaskMutator implements IntrTodoListServiceTest {
@Test
public void
should_add_tasks_in_date_when_date_is_null(){
//test definition
}
}
}[/java]
Les tests imbriquées vont être exécutées en même temps que ceux de leur classe englobante.
Extensions
Le principe de JUnit est de fournir un framework de base facilement extensible, ce qui permet aux utilisateurs d’agir plus rapidement que les développeurs de l’API. Cette caractéristique permet de construire une API qui sert de base pour des bibliothèques tierces.
Suivant ce principe, JUnit 5 permet d’étendre l’API en utilisant
qui vient à remplacer les @Rule de Junit 4. Bien qu’il soit nécessaire de coder l’extension, il est facile d’utiliser des autres frameworks comme complément aux tests JUnit. Par exemple, avec Mockito, il suffit de faire :
@ExtendWith(Extension.class) ,
[java]@ExtendWith(MockitoExtension.class)
public class MockedTodoListServiceTest {
…
@Mock Supplier<List<Task>> todoList;
when(todoList.get()).thenReturn(emptyList());
…
}[/java]
Remarques finales
JUnit 5 a été conçu de manière modulaire (probablement en prévision de Java 9) et la première version milestone est censé sortir fin Q1 2016. Les modules existants sont :
-
junit-commons
-
junit-console
-
junit-engine-api
-
junit-gradle
-
junit-launcher
-
junit4-engine
-
junit4-runner
-
junit5-api
-
junit5-engine
-
surefire-junit5
De façon générale, l’idée a été bien reçue dans la communauté des développeurs. Cependant, il existe déjà d’autres frameworks qui font ce que JUnit5 veut atteindre et des caractéristiques existantes en JUnit 4 qui ne sont pas encore présentes dans JUnit 5.
Le framework n’est pas encore fini et il est possible de faire des suggestions au travers des tickets du projet GitHub. Il y a même eu des propositions intéressantes comme le support d’injection des méthodes via spring Beans ou celle de créer un standard des tests sur la JVM (Open Test Alliance Initiative) en contact avec :
-
Test NG
-
Hamcrest
-
AssertJ
-
Spock
-
Google Truth
-
ScalaTest
-
Eclipse
-
IntelliJ
-
Gradle
-
Maven Surefire Plugin
Nous attendons avec impatience de tester le premier milestone.
Commentaire
2 réponses pour " JUnit 5 rencontre lambda "
Published by Benoit Lafontaine , Il y a 7 ans
Il y a un problème avec l’encodage. il reste des » » et autres dans le code.
Du coup, en Java, je me dis que ça ne compile pas :)
Published by Diana Ortega , Il y a 7 ans
Merci beaucoup pour tes remarques.