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

  • 831