
JUnit 5 je ďalšou generáciou JUnit-u. Vytvára nový základ pre vývojárov a QA inžinierov. Je určený pre testovanie na JVM. Celý je postavený na Java 8 a vyšsie. Tím JUnit 5 vydal Milestone 3 dňa 30. novembra 2016 a v súčasnosti pracuje na ďalších míľnikoch a všeobecne dostupnom release.
Porovnanie JUnit 4 a JUnit 5 v skratke
JUnit 4
- Release už pred desiatimi rokmi
- Distribuované formou – všetko v jednom jar súbore
- Údržba a update sa stáva po 10 rokoch takmer neudržateľná

JUnit 5
- Distribuované vo viacerých jar súboroch:
- JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- Java 8+

- JUnit Platform slúži ako základ pre spustenie testov na JVM
- JUnit Jupiter je kombinácia nového programovacieho modelu a rozšírenie modelu pre písanie testov
- JUnit Vintage poskytuje TestEngine pre spustenie testov založených na starších verziách JUnit 3 a JUnit 4
Prvé dotyky s JUnit 5
Pre JUnit 5 – Milestone 3, je integrácia s Java IDE plne k dispozícii len pre IntelliJ. Ostatné IDE ako Eclipse, tam sa integrácia zatiaľ vykonáva pomocou maven-surefire-plugin. Pozrime sa na pom.xml, ktoré v tomto tvare zaručí úspešný štart našich prvých experimentov s JUnit 5.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>junit5-pg</groupId>
<artifactId>junit5-pg</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<junit.version>4.12</junit.version>
<junit.jupiter.version>5.0.0-M3</junit.jupiter.version>
<junit.vintage.version>${junit.version}.0-M3</junit.vintage.version>
<junit.platform.version>1.0.0-M3</junit.platform.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<includes>
<include>**/Test*.java</include>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
<include>**/*TestCase.java</include>
</includes>
<properties>
<!-- <includeTags>fast</includeTags> -->
<excludeTags>slow</excludeTags>
</properties>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.vintage.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.vintage.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.0.0-M3</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console</artifactId>
<version>1.0.0-M3</version>
</dependency>
</dependencies>
</project>
Začneme s testom triedy Calculator.java
package com.example;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtraction(int a, int b) {
return a - b;
}
}
Prvá JUnit 5 testovacia trieda, FirstTest.java
package com.example.test;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.*;
import com.example.Calculator;
@Tag("fast")
class FirstTest {
static String staticSemafor = null;
String semafor = null;
@BeforeAll
static void beforeAll() {
staticSemafor = "abc";
System.out.println("before all stuff...");
}
@AfterAll
static void afterAll() {
System.out.println("after all stuff...");
}
@BeforeEach
void beforeEach() {
this.semafor = "def";
System.out.println("...before each stuff...");
}
@AfterEach
void aftreEach() {
System.out.println("...after each stuff...");
}
@Test
@DisplayName("My 1st JUnit 5 test! 😎")
void myFirstTest(TestInfo testInfo) {
Calculator calculator = new Calculator();
assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
assertEquals("My 1st JUnit 5 test! 😎", testInfo.getDisplayName(), () -> "TestInfo is injected correctly");
}
@Test
@DisplayName("Semafor and calculator Test")
void semaforTest(TestInfo testInfo) {
Calculator calculator = new Calculator();
assertAll("All assertions",
() -> assertNotNull(FirstTest.staticSemafor),
() -> assertNotNull(this.semafor),
() -> assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"),
() -> assertEquals("Semafor and calculator Test", testInfo.getDisplayName(), () -> "TestInfo is injected correctly")
);
}
@Disabled
@Test
@DisplayName("My Disabled JUnit 5 test!")
void myDisabledTest(TestInfo testInfo) {
Calculator calculator = new Calculator();
assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
}
}
A takto vyzerajú výsledky testu…

Nové anotácie JUnit 5
- @Tag – sa používa na označovanie testov, ktorý je neskôr použiteľný na filtrovanie spúšťaných testov cez pom.xml. Táto anotácia sa používa buď na úrovni triedy alebo metódy. Napríklad ako @Category(SmokeTests.class) v JUnit 4
- @DisplayName – Deklaruje vlastný názov pre testovaciu triedu alebo testovaciu metódu. Konečne J
- @BeforeAll – analogicky k JUnit 4 @BeforeClass
- @AfterAll – analogicky k JUnit 4 @AfterClass
- @BeforeEach – analogicky k JUnit 4 @Before
- @AfterEach – analogicky k JUnit 4 @After
- @Disabled – analogicky k JUnit 4 @Ignore
- TestInfo – je priama náhrada za “TestName” z JUnit 4.
- assertAll – nové varianty Assertions.assertAll (), ktoré akceptujú Java 8 streamy, napr.: Stream<Executable>
Migrácia testov z JUnit 4 na JUnit 5 by teda mohla vyzerať nasledovne
- @Before a @After => @BeforeEach a @AfterEach
- @BeforeClass a @AfterClass => @BeforeAll a @AfterAll
- @Ignore => @Disabled
- @Category => @Tag
- @RunWith => @ExtendWith.
- @Rule a @ClassRule => @ExtendWith
- @Test(timeout = 1000) anotácia už nie je podporovaná
V nasledujúcom texte si ukážeme základné rozdiely pre testovanie timeout a náhradu za @Rule a @ClassRule
Migrácia pre timeout testovanie
JUnit 4:
@Test(timeout@Test(timeout=100)
public void sleep100() {
Thread.sleep(100);
}
JUnit 5:
@Test
void sleep100() {
assertTimeout(ofMillis(1), () -> {
Thread.sleep(100);
});
}
Migrácia prostredníctvom @ExtendWith
Túto novú feature si ukážeme na triede TimingExtension.java, ktorá poskytuje údaje o trvaní behu jednotlivých testov.
import java.lang.reflect.Method;
import java.util.logging.Logger;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.TestExtensionContext;
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
private static final Logger LOG = Logger.getLogger(TimingExtension.class.getName());
@Override
public void beforeTestExecution(TestExtensionContext context) throws Exception {
getStore(context).put(context.getTestMethod().get(), System.currentTimeMillis());
}
@Override
public void afterTestExecution(TestExtensionContext context) throws Exception {
Method testMethod = context.getTestMethod().get();
long start = getStore(context).remove(testMethod, long.class);
long duration = System.currentTimeMillis() - start;
LOG.info(() -> String.format("Method [%s] took %s ms.", testMethod.getName(), duration));
}
private Store getStore(TestExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context));
}
}
A tu je použite TimingExtension v triede TimingExtensionTest.java
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(TimingExtension.class)
@DisplayName("Timing test example")
public class TimingExtensionTests {
@Test
@DisplayName("Sleep for 20 [ms]")
void sleep20ms() throws Exception {
Thread.sleep(20);
}
@Test
@DisplayName("Sleep for 50 [ms]")
void sleep50ms() throws Exception {
Thread.sleep(50);
}
}
Výsledok behu takýchto testov potom vyzerá takto:

Nové vlastnosti JUnit 5 – TestReporter
TestReporter umožňuje prezentáciu informácií o práve bežiacich testoch. Pozrite si príklad.
import java.util.HashMap;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;
class TestReporterDemo {
@Test
void reportSingleValue(TestReporter testReporter) {
testReporter.publishEntry("a key", "a value");
}
@Test
@DisplayName("Report Several Values")
void reportSeveralValues(TestReporter testReporter, TestInfo ti) {
HashMap<String, String> values = new HashMap<>();
values.put("user name", "dk38");
values.put("award year", "1974");
testReporter.publishEntry(values);
testReporter.publishEntry("Test", ti.getDisplayName());
}
}
Nové vlastnosti JUnit 5 – Nested tests
Nested testy umožňujú vyjadrovať vzťahy medzi niekoľkými skupinami testov. Pozrite si príklad
import org.junit.jupiter.api.*;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Stack;
import static java.time.Duration.ofMillis;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("A stack")
@Tag("fast")
public class AStackTest {
Stack<Object> stack;
List<String> list;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
System.out.println("BeforeEach... stack init");
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, () -> stack.pop());
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, () -> stack.peek());
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
void timeoutExceeded() {
assertTimeout(ofMillis(1), () -> {
assertAll("All",
() -> assertEquals(anElement, stack.pop()),
() -> assertTrue(stack.isEmpty())
);
});
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped(TestInfo testInfo) {
assertAll("Return Element When Peeked",
() -> assertEquals("returns the element when popped and is empty", testInfo.getDisplayName(), () -> "TestInfo is injected correctly"),
() -> assertEquals(anElement, stack.pop()),
() -> assertTrue(stack.isEmpty())
);
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked(TestInfo testInfo) {
assertAll("Return Element When Peeked",
() -> assertEquals("returns the element when peeked but remains not empty", testInfo.getDisplayName(), () -> "TestInfo is injected correctly"),
() -> assertEquals(anElement, stack.peek()),
() -> assertFalse(stack.isEmpty())
);
}
A výsledok, takýchto nested testov

Nové vlastnosti JUnit 5 – Dynamické testy
Dynamické testy sú testy, ktorá sa generujú za behu. Spustiteľné sú prostredníctvom @FunctionalInterface čo znamená, že implementácia dynamického testu môže byť vykonaná prostredníctvom Java 8 ako lambda expression, alebo ako priama referencia metódy. V JUnit 4 sme na takýto druh testov potrebovali frameworky tretích strán, ako napríklad JUnit-QuickCheck. Taký “property test” v JUnit 4 vyzeral potom takto:
@Property(trials = 5)
public void testAddition(int number) {
System.out.println("Generated number for testAddition: " + number);
Calculator calculator = new Calculator();
calculator.add(number);
assertEquals(calculator.getResult(), number);
}
Ako dynamický test vyzerá v JUnit 5 sa pozrieme cez triedu MyDynamicTest.java
import com.example.Calculator;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
@DisplayName("My Dynamic test class")
@ExtendWith(TimingExtension.class)
public class MyDynamicTest {
Calculator calc;
@BeforeEach
void initCalc() {
calc = new Calculator();
}
@TestFactory
@DisplayName("Dynamic test method add")
Stream<DynamicTest> myDynamicTestAdd(TestReporter testReporter) {
Random rand = new Random();
return IntStream.iterate(1, n -> n + rand.nextInt()).limit(10000).mapToObj(
n -> dynamicTest("test for random int: " + n, () -> assertTrue( calc.add(n, n+1) == n+ n + 1))
);
}
@TestFactory
@DisplayName("Dynamic test method substraction")
Stream<DynamicTest> myDynamicTestSubst(TestReporter testReporter) {
Random rand = new Random();
return IntStream.iterate(1, n -> n + rand.nextInt()).limit(10000).mapToObj(
n -> dynamicTest("test for random int: " + n, () -> assertTrue( calc.subtraction(n, n+1) == n- (n + 1)))
);
}
}
A jeho výsledok takto:




