BBL JUnit 5
Juillet 2017
Erwann Thebault - @ethebault
Inspiré du quickie
Benoit Averty - @Kaidjin
Le doute est le signe de la certitude.
Citation de Alain, Propos sur l'éducation (1932)
JUnit 4
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.???</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
- 4.12 ->Décembre 2014
- 4.0 -> Février 2006
Nombre de projets utilisant le framework parmi les 3862 premiers projets sur github
JUnit 5
JUnit 5
- Kickstarter en 2015
- Première milestone en Juillet 2016
- Release prévue pour le troisième trimestre !
Text
Text
API de test
Plateforme Junit
Retrocompatibilité JUnit 4
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M4</version>
</dependency>
@Test
public void myFirstTest(){
// Ça change pas trop !!!
}
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
public void myFirstTest(){
// Ça change pas trop !!!
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
public void myFirstTest(){
// Ça change pas trop !!!
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
BeforeClass
Before
After
AfterClass
@Test
@Ignore
public void myFirstTest(){
// Ça change pas trop !!!
}
@Test
@Disabled
public void myFirstTest(){
// Ça change pas trop !!!
}
JUnit4
JUnit5
@Test
public void shouldWorkBecauseItWorksOnMyComputer(){
}
@Test
public void should_work_because_it_works_on_my_computer(){
}
camelCase
snake_case
CamelCase ou snake_case ?
@Test
@DisplayName("Avec plein d'emoj, ca ne peut que marcher \uD83D\uDC30 \uD83D\uDC31 \uD83D\uDC3C \uD83D\uDC19")
public void shouldWorkBecauseThereAreEmoji(){
}
JUnit 5
public class MyTest {
@BeforeEach
public void setup() {}
@Nested
class NominalTest{
@BeforeEach
public void setup(){}
@Test
public void test(){}
}
@Nested
class InvalidInputTest{
@BeforeEach
public void setup(){}
@Test
public void test(){}
}
@Test
public void test(){
}
}
Tests imbriqués
@RepeatedTest(10)
@DisplayName("Test en répétition")
public void isInferiorTo5(TestInfo testInfo, RepetitionInfo repetitionInfo){
int value = 5;
System.out.print("Test "+ testInfo.getTestMethod().get().getName()
+ "( " + repetitionInfo.getCurrentRepetition()
+ " de " + repetitionInfo.getTotalRepetitions() +" )");
assertThat(5).isGreaterThan(repetitionInfo.getCurrentRepetition());
}
Repeated test
Test paramétrés
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameterized.Parameters(name = "{index}: fib({0})={1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 },
{ 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
private int input;
private int expected;
public FibonacciTest(int input, int expected) {
this.input = input;
this.expected = expected;
}
@Test
public void test() {
Assert.assertEquals(expected, Fibonacci.compute(input));
}
}
JUnit 4 : Passage par constructeur
@RunWith(Parameterized.class)
public class Fibonacci2Test {
@Parameterized.Parameters(name = "{index}: fib({0})={1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
@Parameterized.Parameter // first data value (0) is default
public int fInput;
@Parameterized.Parameter(1)
public int fExpected;
@Test
public void test() {
Assert.assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
JUnit 4 : Passage par injection
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.0.0-M4</version>
</dependency>
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertNotNull(argument);
}
JUnit 5 : Injection de valeurs primitives
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
assertNotNull(timeUnit.name());
}
JUnit 5 : Injection d'une énumération
@ParameterizedTest
@MethodSource(names = "stringAndIntProvider")
void testWithMultiArgMethodSource(int input, int expected) {
assertEquals(expected, Fibonacci.compute(input));
}
static Stream<Arguments> stringAndIntProvider() {
return Stream.of(
new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 1},
new int[]{3, 2}, new int[]{4, 3}, new int[]{5, 5}, new int[]{6, 8})
.map( prop -> ObjectArrayArguments.create(prop[0],prop[1]));
}
JUnit5 : Injection par une méthode provider
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithMultiArgMethodSource(int input, int expected) {
assertEquals(expected, Fibonacci.compute(input));
}
static class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> arguments(ContainerExtensionContext context) {
return Stream.of(
new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 1},
new int[]{3, 2}, new int[]{4, 3}, new int[]{5, 5}, new int[]{6, 8})
.map( prop -> ObjectArrayArguments.create(prop[0],prop[1]));
}
}
JUnit5 : Injection par un provider d'argument
@ParameterizedTest
@CsvSource({ "0,0","1,1", "2,1", "3,2", "4,3", "5,5", "6,8"})
void testWithCsvSource(int input, int expected) {
assertEquals(expected, Fibonacci.compute(input));
}
JUnit5 : Injection par CSV
JUnit5 : Injection par fichier CSV
@ParameterizedTest
@CsvFileSource(resources = "/fibo.csv")
void testWithCsvSource(int input, int expected) {
assertEquals(expected, Fibonacci.compute(input));
}
Test Factory
@TestFactory
Stream<DynamicTest> dynamicTestsFromCollection() {
Fibonacci fibo = new Fibonacci();
return Stream.of(new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 1},
new int[]{3, 2}, new int[]{4, 3}, new int[]{5, 5}, new int[]{6, 8})
.map(
input ->
dynamicTest("Compute fibonacci for " + input[0],
() -> assertEquals(input[1], fibo.compute(input[0])))
);
}
Evolution des assertions
@Test
public void myFirstTest(){
org.junit.Assert.assertEquals(2, 1 + 1);
}
@Test
public void myFirstTest(){
org.junit.jupiter.api.Assertions.assertEquals(2, 1 + 1);
}
JUnit4
JUnit5
@Test
void myFirstTest(){
assertEquals("vendredi", "lundi",
() -> "oh non, on est que lundi");
}
JUnit5
@Test
public void myFirstTest(){
// base sur Hamcrest
Assert.assertThat(Arrays.asList("Junit", "TestNG"),
CoreMatchers.hasItem("Junit"));
}
@Test
public void myFirstTest(){
assertThat(Arrays.asList("JUnit", "TestNG")).contains("JUnit");
}
JUnit4
AssertJ
@Test
public void testURL() throws MalformedURLException {
assertThat(new URL("http://google.fr")).hasNoPort();
assertThat(new URL("http://google.fr")).hasHost("google.fr");
assertThat(new URL("http://google.fr")).hasNoUserInfo();
}
@Test
public void testFile(){
assertThat(new File("sampling.txt")).exists();
assertThat(new File("sampling.txt")).isFile();
}
AssertJ
AssertJ
@Test
public void testInt(){
assertThat(25).isBetween(12,45);
assertThat(25).isCloseTo(20, Offset.offset(5));
}
AssertJ
@Test(expected = NullPointerException.class)
public void myFisrtTestWithException(){
String value = null;
value.split(";");
}
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void myFirstTestException(){
exception.expect(IllegalArgumentException.class);
exception.expectMessage(
CoreMatchers.containsString("Invalid age"));
throw new IllegalArgumentException("Invalid age");
}
Junit 4 : gestion d'exception
Junit 4 : gestion d'exception avancée
Throwable exception = assertThrows(IllegalArgumentException.class,
() -> {
throw new IllegalArgumentException("Invalid age");
}
);
assertEquals("Invalid age", exception.getMessage());
JUnit 5 : gestion d'exception
@Test(timeout = 10000)
public void testLast10s(){
// too long !!!
}
JUnit 4 : timeout unitaire
@Rule
public Timeout timeout = Timeout.seconds(10);
@Test
public void testLast10s2() throws InterruptedException {
TimeUnit.SECONDS.sleep(15);
}
JUnit 4 : timeout global
@Test
public void timeoutNotExceeded(){
assertTimeout(ofSeconds(5), () -> {
TimeUnit.SECONDS.sleep(10);
});
}
JUnit 5 : timeout
@Test
void timeoutNotExceededWithResult() {
String actualResult = assertTimeout(ofSeconds(5), () -> {
return "a result";
});
assertEquals("a result", actualResult);
}
JUnit 5 : timeout avec retour de valeur
@Test
void timeoutExceededWithPreemptiveTermination() {
assertTimeoutPreemptively(ofSeconds(2), () -> {
TimeUnit.SECONDS.sleep(5);
});
}
JUnit 5 : timeout avec arrêt préemptif
@Test
public void testAssertAll(){
assertAll("address",
() -> assertEquals("JUnit", "JUnit1"),
() -> assertEquals("TestNG", "TestNG"),
() -> assertEquals("EasyMock", "Mockito")
);
}
Assertions groupées
Expected :JUnit
Actual :JUnit1
<Click to see difference>
Expected :EasyMock
Actual :Mockito
<Click to see difference>
org.opentest4j.MultipleFailuresError: address (2 failures)
expected: <JUnit> but was: <JUnit1>
expected: <EasyMock> but was: <Mockito>
Résultat
Extensions
@RunWith(MockitoJUnitRunner.class)
public class MyMockitoTest {
@Mock
private Fibonacci fiBoMock;
@Test
public void should_be_verified() {
//Given
Mockito.when(fiBoMock.compute(1)).thenReturn(1);
//When
int compute = fiBoMock.compute(1);
//Then
Assert.assertEquals(1, compute);
Mockito.verify(fiBoMock, Mockito.times(1)).compute(1);
}
}
JUnit 4 : Mockito
@RunWith(MockitoJUnitRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MyMockitoTest {
@Mock
private Fibonacci fiBoMock;
@Test
public void should_be_verified() {
//Given
Mockito.when(fiBoMock.compute(1)).thenReturn(1);
//When
int compute = fiBoMock.compute(1);
//Then
Assert.assertEquals(1, compute);
Mockito.verify(fiBoMock, Mockito.times(1)).compute(1);
}
}
JUnit 4 : Mockito
Un seul runner a la fois
@RunWith(MockitoJUnitRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MyMockitoTest {
@Mock
private Fibonacci fiBoMock;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void should_be_verified() {
//Given
Mockito.when(fiBoMock.compute(1)).thenReturn(1);
//When
int compute = fiBoMock.compute(1);
//Then
Assert.assertEquals(1, compute);
Mockito.verify(fiBoMock, Mockito.times(1)).compute(1);
}
}
JUnit 4 : Mockito
Initialisation manuelle
@ExtendWith(MockitoExtension.class)
public class MyMockitoTest {
@Mock
private Fibonacci fiBoMock;
@Test
public void should_be_verified() {
//Given
Mockito.when(fiBoMock.compute(1)).thenReturn(1);
//When
int compute = fiBoMock.compute(1);
//Then
Assertions.assertEquals(1, compute);
Mockito.verify(fiBoMock, Mockito.times(1)).compute(1);
}
}
JUnit 5 : Mockito
@ExtendWith(AnotherExtension.class)
@ExtendWith(MockitoExtension.class)
public class MyMockitoTest {
@Mock
private Fibonacci fiBoMock;
@Test
public void should_be_verified() {
//Given
Mockito.when(fiBoMock.compute(1)).thenReturn(1);
//When
int compute = fiBoMock.compute(1);
//Then
Assertions.assertEquals(1, compute);
Mockito.verify(fiBoMock, Mockito.times(1)).compute(1);
}
}
JUnit 5 : Mockito
Pas de limitation
à une extension
Intégration aux outils
Runtime
- Java 8
IDE
- Intellij Idea 2016.2
- Eclipse 4.7 (Oxygen) (Beta)
Intégration à Maven
...
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.0.0-M4</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
...
<dependencies>
...
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M4</version>
<scope>test</scope>
</dependency>
</dependencies>
...
Compatibilité JUnit4 avec JUnit5
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>4.12.0-M4</version>
<scope>test</scope>
</dependency>
Limitation au niveau des Rules
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-migrationsupport</artifactId>
<version>5.0.0-M4</version>
</dependency>
Compatibilité JUnit5 avec JUnit4
<!-- run Junit5 tests with JUnit 4 -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.0.0-M4</version>
</dependency>
@RunWith(JUnitPlatform.class)
public class JunitPlateformRunnerTest {
@org.junit.jupiter.api.Test
public void should_be_true(){
Assertions.assertTrue(true);
}
}
Conclusion
- Pas de changement révolutionnaire
- Sémantique plus claire
- Mais
- API en évolution
- Intégrations incomplètes
- Extensions limitées
Merci
BBL JUnit5
By erwann thebault
BBL JUnit5
BBL sur JUnit5
- 912