JUnit 5 : Tests Imbriqués Dynamiques
(et autres nouveautés)
Benoit Averty - @Kaidjin
Innovation
Junit 4.12
Décembre 2014
Mai 2012
Junit 4.11
Fin 2017
Junit 5
Au moins 1000 frameworks javascript !
Création de Junit - Fin des années 90
Monopole ?
Junit
TestNG
2412
234
Nombre de projets utilisant le framework parmi les 3862 premiers projets sur github
Junit 5
- Kickstarter en 2015
- première milestone en Juillet 2016
- Release au troisième trimestre 2017
Il est temps de songer à migrer !
1 Installation
2 Migration des tests
3 Nouvelles fonctionnalités !
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class GreetingsTest {
private Greetings greetings;
@Before
public void setup() {
bcs = new Greetings();
}
@Test
public void getMethodShouldReturnHello() {
final String expected = "Hello!";
final String actual = greetings.getGreetings();
Assert.assertEquals(expected, actual);
}
}
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
Avant
Après
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
Premier test Junit 5
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class Junit5GreetingsTest {
private Greetings bcs;
@BeforeEach
public void setup() {
bcs = new Greetings();
}
@Test
void greetingsMethodShouldReturnHello() {
final String expected = "Hello!";
final String actual = greetings.getGreetings();
Assertions.assertEquals(expected, actual);
}
}
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<dependencies>
<!-- ... -->
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
Récap
APIs utilisées dans les tests
détecte et lance les tests
Implémentation du TestEngine pour junit 4 (et plus vieux)
Implémentation du TestEngine pour junit 5
API utilisée par les outils (IntelliJ, maven...) pour piloter plateforme/engine
Les nouveautés
- @DisplayName
- Tests imbriqués
- Tests dynamiques
- Nouveautés sur les assertions
- Système d'extensions
@DisplayName
@DisplayName("Greetings")
public class Junit5GreetingsTest {
private Greetings greetings;
@BeforeEach
public void setup() { ... }
@Test
@DisplayName("getGreetings() should return \"Hello, world!\"")
void greetingsMethod() { ... }
}
Pas de support dans maven 😞
Tests imbriqués
@DisplayName("Greetings")
public class Junit5GreetingsTest {
@Nested
@DisplayName("getGreetings method")
class GreetingsMethod {
@Test
@DisplayName("Should return \"Hello, world!\"")
public void greetingsMethod() {
}
}
@Nested
@DisplayName("other method")
class Other {
@Test
@DisplayName("Should contain \"Junit5\"")
public void testOtherMethod() {
}
}
}
public class Junit5GreetingsTest {
@BeforeEach
public void setup() { ... }
@Nested
class GreetingsMethod {
@Nested
class NominalCase {
@BeforeEach
void setup() { ... }
@Test
void theTest() { ... }
}
@Nested
class InvalidInput{
@BeforeEach
void setup() { ... }
@Test
void theTest() { ... }
}
}
}
Exécutée une fois chacune
Exécutée deux fois
Tests parametrés et dynamiques
Laborieux avec Junit 4
@RunWith(Parameterized.class)
public class GreetingsTest {
@Parameters
public static Collection<Object[]> data() throws URISyntaxException, IOException {
return Files.lines(Paths.get(ClassLoader.getSystemClassLoader().getResource("data.csv").toURI()))
.map(line -> line.split(";"))
.collect(Collectors.toList());
}
public GreetingsTest(String a, String b, String c) {
this.a = Integer.parseInt(a);
this.b = Integer.parseInt(b);
expected = Integer.parseInt(c);
}
private int a;
private int b;
private int expected;
@Test
public void multiplyTest() {
Assert.assertEquals(expected, a*b);
}
}
Incompatible avec autre runner
Constructeur et champs...
💩
@ParameterizedTest
@ParameterizedTest
@CsvFileSource(resources = "data.csv", delimiter = ';')
void testMultiply(long a, long b, long expected) {
long actual = service.multiply(a, b);
Assertions.assertEquals(expected, actual);
}
@ParameterizedTest
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void paramTest(int param) { ... }
@ParameterizedTest
@MethodSource(names = "paramsProvider")
void testWithMultiArgMethodSource(String first, int second) { ... }
static Stream<Arguments> paramsProvider() {
return Stream.of(
ObjectArrayArguments.create("foo", 1),
ObjectArrayArguments.create("bar", 2)
);
}
Et aussi @CsvSource, @EnumSource, @ArgumentsSource
@TestFactory
@TestFactory
Stream<DynamicTest> testWithCsv() {
return Files.lines(path)
.map(line -> line.split(";"))
.map(args ->
dynamicTest(args[0]+"*"+args[1]+" should equal "+args[2],
() -> {
Assertions.assertEquals(service.multiply(args[0],args[1]), args[2]);
}
));
}
Assertions
Enfin des assertions simples pour les exceptions !
Assertions.assertThrows(ArithmeticException.class, () -> service.divide(5, 0));
org.opentest4j.AssertionFailedError: Expected java.lang.ArithmeticException to be thrown,
but nothing was thrown.
Assertions
Assertions multiples
Assertions.assertAll("Customer doesn't have the right specs",
() -> assertEquals(customer.getId(), 123L, "Incorrect ID"),
() -> assertEquals(customer.getName(), "JohnDoe", "Incorrect Name")
);
org.opentest4j.MultipleFailuresError: Customer doesn't have the right specs (2 failures)
Incorrect ID ==> expected: <1234> but was: <123>
Incorrect name ==> expected: <John Doe> but was: <JohnDoe>
Meilleur modèle d'extensions
@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class SpringAndMockitoIntegrationTest {
@Mock
private ArithmeticsService service;
@Test
public void aTest() {
...
}
}
Deux extensions sur une même classe ! 🎉
Remplace le concept de "Runner"
Conclusion
- Pas révolutionnaire
- Beaucoup de choses rendues plus faciles / plus agréables
- Migration facile et peu coûteuse
Il est temps de songer à migrer !
Merci !
JUnit 5 : Tests Imbriqués Dynamiques
By Benoit Averty
JUnit 5 : Tests Imbriqués Dynamiques
- 1,266