Un guide de JUnit 5

1. Vue d'ensemble

JUnit est l'un des frameworks de tests unitaires les plus populaires de l'écosystème Java. La version JUnit 5 contient un certain nombre d'innovations intéressantes, dans le but de prendre en charge les nouvelles fonctionnalités de Java 8 et supérieur , ainsi que de permettre de nombreux styles de test différents.

2. Dépendances de Maven

La configuration de JUnit 5.x.0 est assez simple, nous devons ajouter la dépendance suivante à notre pom.xml :

 org.junit.jupiter junit-jupiter-engine 5.1.0 test 

Il est important de noter que cette version nécessite Java 8 pour fonctionner .

De plus, il existe désormais une prise en charge directe pour exécuter des tests unitaires sur la plate-forme JUnit dans Eclipse ainsi que dans IntelliJ. Vous pouvez, bien sûr, également exécuter des tests à l'aide de l'objectif Maven Test.

D'autre part, IntelliJ prend en charge JUnit 5 par défaut. Par conséquent, exécuter JUnit 5 sur IntelliJ est assez simple, faites simplement un clic droit -> Exécuter ou Ctrl-Shift-F10.

3. Architecture

JUnit 5 est composé de plusieurs modules différents issus de trois sous-projets différents:

3.1. Plateforme JUnit

La plateforme est responsable du lancement des frameworks de test sur la JVM. Il définit une interface stable et puissante entre JUnit et son client comme les outils de construction.

L'objectif final est de savoir comment ses clients s'intègrent facilement à JUnit pour découvrir et exécuter les tests.

Il définit également l'API TestEngine pour développer un cadre de test qui s'exécute sur la plate-forme JUnit. Par cela, vous pouvez brancher des bibliothèques de test tierces, directement dans JUnit, en implémentant TestEngine personnalisé.

3.2. JUnit Jupiter

Ce module comprend de nouveaux modèles de programmation et d'extension pour l'écriture de tests dans JUnit 5. Les nouvelles annotations par rapport à JUnit 4 sont:

  • @TestFactory - désigne une méthode qui est une usine de test pour les tests dynamiques
  • @DisplayName - définit le nom d'affichage personnalisé pour une classe de test ou une méthode de test
  • @Nested - indique que la classe annotée est une classe de test imbriquée et non statique
  • @Tag - déclare des balises pour les tests de filtrage
  • @ExtendWith - il est utilisé pour enregistrer des extensions personnalisées
  • @BeforeEach - indique que la méthode annotée sera exécutée avant chaque méthode de test (précédemment @Before )
  • @AfterEach - indique que la méthode annotée sera exécutée après chaque méthode de test (précédemment @After )
  • @BeforeAll - indique que la méthode annotée sera exécutée avant toutes les méthodes de test de la classe actuelle (précédemment @BeforeClass )
  • @AfterAll - indique que la méthode annotée sera exécutée après toutes les méthodes de test de la classe actuelle (précédemment @AfterClass )
  • @Disable - il est utilisé pour désactiver une classe ou une méthode de test (précédemment @Ignore )

3.3. JUnit Vintage

Prend en charge l'exécution de tests basés sur JUnit 3 et JUnit 4 sur la plate-forme JUnit 5.

4. Annotations de base

Pour discuter des nouvelles annotations, nous avons divisé la section dans les groupes suivants, responsables de l'exécution: avant les tests, pendant les tests (optionnel) et après les tests:

4.1. @BeforeAll et @BeforeEach

Voici un exemple de code simple à exécuter avant les principaux cas de test:

@BeforeAll static void setup() { log.info("@BeforeAll - executes once before all test methods in this class"); } @BeforeEach void init() { log.info("@BeforeEach - executes before each test method in this class"); }

Il est important de noter que la méthode avec l' annotation @BeforeAll doit être statique, sinon le code ne sera pas compilé.

4.2. @DisplayName et @Disabled

Passons à de nouvelles méthodes de test optionnelles:

@DisplayName("Single test successful") @Test void testSingleSuccessTest() { log.info("Success"); } @Test @Disabled("Not implemented yet") void testShowSomething() { }

Comme nous pouvons le voir, nous pouvons changer le nom d'affichage ou désactiver la méthode avec un commentaire, en utilisant de nouvelles annotations.

4.3. @AfterEach et @AfterAll

Enfin, discutons des méthodes liées aux opérations après l'exécution des tests:

@AfterEach void tearDown() { log.info("@AfterEach - executed after each test method."); } @AfterAll static void done() { log.info("@AfterAll - executed after all test methods."); }

Veuillez noter que la méthode avec @AfterAll doit également être une méthode statique.

5. Assertions et hypothèses

JUnit 5 essaie de tirer pleinement parti des nouvelles fonctionnalités de Java 8, en particulier les expressions lambda.

5.1. Assertions

Les assertions ont été déplacées vers org.junit.jupiter.api.Assertions et ont été considérablement améliorées. Comme mentionné précédemment, vous pouvez désormais utiliser des lambdas dans les assertions:

@Test void lambdaExpressions() { assertTrue(Stream.of(1, 2, 3) .stream() .mapToInt(i -> i) .sum() > 5, () -> "Sum should be greater than 5"); }

Bien que l'exemple ci-dessus soit trivial, un avantage de l'utilisation de l'expression lambda pour le message d'assertion est qu'elle est évaluée paresseusement, ce qui peut économiser du temps et des ressources si la construction du message est coûteuse.

It is also now possible to group assertions with assertAll() which will report any failed assertions within the group with a MultipleFailuresError:

 @Test void groupAssertions() { int[] numbers = {0, 1, 2, 3, 4}; assertAll("numbers", () -> assertEquals(numbers[0], 1), () -> assertEquals(numbers[3], 3), () -> assertEquals(numbers[4], 1) ); }

This means it is now safer to make more complex assertions, as you will be able to pinpoint the exact location of any failure.

5.2. Assumptions

Assumptions are used to run tests only if certain conditions are met. This is typically used for external conditions that are required for the test to run properly, but which are not directly related to whatever is being tested.

You can declare an assumption with assumeTrue(), assumeFalse(), and assumingThat().

@Test void trueAssumption() { assumeTrue(5 > 1); assertEquals(5 + 2, 7); } @Test void falseAssumption() { assumeFalse(5  assertEquals(2 + 2, 4) ); }

If an assumption fails, a TestAbortedException is thrown and the test is simply skipped.

Assumptions also understand lambda expressions.

6. Exception Testing

There are two ways of exception testing in JUnit 5. Both of them can be implemented by using assertThrows() method:

@Test void shouldThrowException() { Throwable exception = assertThrows(UnsupportedOperationException.class, () -> { throw new UnsupportedOperationException("Not supported"); }); assertEquals(exception.getMessage(), "Not supported"); } @Test void assertThrowsException() { String str = null; assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }

The first example is used to verify more detail of the thrown exception and the second one just validates the type of exception.

7. Test Suites

To continue the new features of JUnit 5, we will try to get to know the concept of aggregating multiple test classes in a test suite so that we can run those together. JUnit 5 provides two annotations: @SelectPackages and @SelectClasses to create test suites.

Keep in mind that at this early stage most IDEs do not support those features.

Let's have a look at the first one:

@RunWith(JUnitPlatform.class) @SelectPackages("com.baeldung") public class AllUnitTest {}

@SelectPackage is used to specify the names of packages to be selected when running a test suite. In our example, it will run all test. The second annotation, @SelectClasses, is used to specify the classes to be selected when running a test suite:

@RunWith(JUnitPlatform.class) @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class}) public class AllUnitTest {}

For example, above class will create a suite contains three test classes. Please note that the classes don't have to be in one single package.

8. Dynamic Tests

The last topic that we want to introduce is JUnit 5 Dynamic Tests feature, which allows to declare and run test cases generated at run-time. In contrary to the Static Tests which defines fixed number of test cases at the compile time, the Dynamic Tests allow us to define the tests case dynamically in the runtime.

Dynamic tests can be generated by a factory method annotated with @TestFactory. Let's have a look at the code example:

@TestFactory public Stream translateDynamicTestsFromStream() { return in.stream() .map(word -> DynamicTest.dynamicTest("Test translate " + word, () -> { int id = in.indexOf(word); assertEquals(out.get(id), translate(word)); }) ); }

This example is very straightforward and easy to understand. We want to translate words using two ArrayList, named in and out, respectively. The factory method must return a Stream, Collection, Iterable, or Iterator. In our case, we choose Java 8 Stream.

Please note that @TestFactory methods must not be private or static. The number of tests is dynamic, and it depends on the ArrayList size.

9. Conclusion

La rédaction était un bref aperçu des changements à venir avec JUnit 5.

Nous pouvons voir que JUnit 5 a un grand changement dans son architecture qui concerne le lanceur de plate-forme, l'intégration avec l'outil de construction, l'IDE, d'autres frameworks de test unitaires, etc. De plus, JUnit 5 est plus intégré avec Java 8, en particulier avec les concepts Lambdas et Stream .

Les exemples utilisés dans cet article se trouvent dans le projet GitHub.