Publié par
Il y a 2 années · 8 minutes · Craft

JUnit 5 rencontre lambda

JUnit 5 : rencontre lambdaJUnit 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 :

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 :

apply plugin: 'org.junit.gen5.gradle'

et les bibliothèques de JUnit5 :

dependencies {
 classpath 'org.junit:junit-gradle:5.0.0-SNAPSHOT'
}

Rétrocompatibilité

Il est possible d’utiliser JUnit 5 et JUnit 4 ensemble avec le flag
runJunit4=true
et en déclarant les bibliothèques de JUnit 4 comme dépendances.

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'
}

Pour le moment, il est possible d’exécuter les tests en utilisant soit

gradle junit5Test

soit

gradle check

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
Task
avec un nom, une priorité et une date de fin :

class Task {
  private String name;
  private Priority priority;
  private LocalDate date;
  …
}

La classe
TodoListService 
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.

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();
}
...
}

Écrire un test avec JUnit5

Les classes pour l’exécution de JUnit test se trouvent sous le package
org.junit.gen5.api
. Dans le fichier gradle, on a défini le pattern pour les tests:  .*Test . Il est désormais possible de mettre un Tag dans les tests avec l’annotation

@Tag("TAG")

qui sert à filtrer les tests a posteriori.

Il est possible aussi d’ajouter un message en cas d’erreur avec l’annotation

@DisplayName("Test Name")

Dans ce cas,
"Test Name"
sert à identifier le test et il sera affiché dans le log si le test échoue.

A l’intérieur de la classe, chaque test est annoté avec
@Test, 
comme on est habitué avec JUnit 4.

@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)));
}

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.

@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() {
…
}

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
Supplier<String>
, c’est-à-dire :

assertNotNull(resp, () -> "Test Failed, asserting not null");

ou

assertTrue(resp.size() == 3, "Collection size must be the same");

AssertAll

Une autre nouvelle caractéristique est la possibilité de grouper les erreurs de plusieurs assertions avec
assertAll
. Par exemple :

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)))
);

Avec
assertAll
toutes les assertions sont exécutées et les erreurs sont rapportées en un seul bloc.

AssertThrows

Une autre assertion très utile est
assertThrows
. On peut constater qu’avec l’utilisation d’
assertThrows
, il est plus simple de vérifier si le code lance une exception. Il n’ est alors plus nécessaire d’écrire une
@Rule
au début du test.

@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))));
}

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:

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));
  }
}

É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:

@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  
   }
 }
}

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
@ExtendWith(Extension.class) , 
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(MockitoExtension.class)
public class MockedTodoListServiceTest {
 ...
 @Mock Supplier<List<Task>> todoList;
 when(todoList.get()).thenReturn(emptyList());
 ...
}

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.

2 réflexions au sujet de « JUnit 5 rencontre lambda »

  1. Publié par Benoit Lafontaine, Il y a 2 années

    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 :)

  2. Publié par Diana Ortega, Il y a 2 années

    Merci beaucoup pour tes remarques.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *