Michael Albrecht
Java CAT & PHP Pussy - team neusta
Sommer 2015
Start "JUnit Lambda"
Integration Java 8 Features
Crowdfunding campaign
leads to JUnit 5
package de.mike.tests;
import org.junit.jupiter.api.Test;
public class NameDerTestKlasse {
@Test
public void irgendeinBeliebigerMethodenname() {
}
}
Die Annotation hat keine Attribute (expected, timeout)!
package de.mike.tests;
import org.junit.jupiter.api.Test;
class NameDerTestKlasse {
@Test
void irgendeinBeliebigerMethodenname() {
}
}
Testklassen und ~methoden müssen
nicht mehr public sein.
java -jar ${dir}/junit-platform-console-standalone-1.0.0-M5.jar
--classpath target/test-classes:target/classes
--include-classname ^.*Demo?$
--select-class de.mike.training.junit5.FirstTestDemo
Eigenes ausführbares Java Archiv:
junit-platform-console-standalone-1.0.0-M5.jar
package de.mike.training.junit5;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
@RunWith(org.junit.platform.runner.JUnitPlatform.class)
class FirstTest {
@Test
void myFirstTest() {
...
}
...
}
Neue Tests können direkt als JUnit 5 Tests erstellt werden.
Run As... ermöglicht als TestRunner auch JUnit 5 zu...
...und der Output wurde entsprechend angepasst.
JUnit 4 | JUnit 5 |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Categories | @Tag |
@Ignore | @Disabled |
package de.mike.training.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class TestFictureDemo {
@BeforeEach
void init() {
// preparation for each test
}
@Test
void test1() {
// test execution 1
}
@Test
void test2() {
// test execution 2
}
@AfterEach
void tearDown() {
// tear down for each test
}
}
JUnit 4 | JUnit 5 |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Categories | @Tag |
@Ignore | @Disabled |
package de.mike.training.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class TestFictureDemo {
@BeforeAll
static void initAll() {
// preparation for all tests
}
@Test
void test1() {
// test execution 1
}
@Test
void test2() {
// test execution 2
}
@AfterAll
static void tearDownAll() {
// tear down for all tests
}
}
JUnit 4 | JUnit 5 |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Categories | @Tag |
@Ignore | @Disabled |
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("complete_test")
public class TaggingDemo {
@Test
@Tag("single_test")
public void test1() {
...
}
@Test
public void test2() {
...
}
@Test
@Tag("single_test")
@Tag("other_test")
public void test3() {
...
}
}
JUnit 4 | JUnit 5 |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Categories | @Tag |
@Ignore | @Disabled |
package de.mike.training.junit5;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
public class IgnoredTestDemo {
@Disabled("Disable this test.")
@Test
public void test() {
// nothing will be done
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("integration")
public @interface IntegrationTest {
}
Kombinationen von Annotationen können in JUnit 5 genutzt werden, um einfachere Bezeichner zu erhalten.
@DisplayName("Dies ist ein Spezialtestfall")
class DisplayNameDemo {
@Test
@DisplayName("Mein Name mit Leerzeichen 😱")
void testWhatever() {
...
}
}
Es können nun für Test Cases und Methoden
bessere Bezeichner (inkl. Leerzeichen, Sonderzeichen und Emojis) benutzt werden.
@Test
void timeoutExceeded() {
// Execution exceeded timeout of 10 ms by e.g. 91 ms
assertTimeout(ofMillis(10), () -> { /* here your test */
});
}
...
Als harter Abbruch...
... oder weiche Prüfung
...
@Test
void timeoutExceededWithPreemptiveTermination() {
// Execution timed out (at the latest) after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> { /* here your test */
});
}
@Test
void myFirstTestWithSupplier() {
assertTrue(1 + 1 == 2, () -> "1 + 1 should equal 2");
assertAll("Check all assertions and report each",
() -> assertTrue(1 + 2 == 5, "1 + 2 should equal 3"),
() -> assertTrue(1 + 3 == 5, "1 + 3 should equal 4"),
() -> assertTrue(2 + 2 == 5, "2 + 2 should equal 4")
);
}
Mit assertAll können mehrere Assertions geprüft werden, bevor evtl. eine fehlschlägt.
@Test
void testMyPerson() {
assertAll("Failure",
() -> {
assertThat(...); /* If this fails, */
assertThat(...); /* this will not be checked */
},
() -> assertNotNull(...); /* but this anyway */
}
}
Natürlich können ab- und unabhängige Assertions miteinander kombiniert werden.
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@Test
void testOnlyOnITestServer() {
assumeTrue("ITest".equals(System.getenv("Staging")));
...
}
@Test
void testOnlyOnCTestServer() {
assumeTrue("CTest".equals(System.getenv("Staging")),
() -> "Aborting test: not on CTest server");
...
}
Werden die Vorannahmen nicht erfüllt,
wird der Test nicht ausgeführt.
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumeThat;
@Test
void testInAllEnvironments() {
assumingThat("CTest".equals(System.getenv("Staging")),
() -> {
// perform these assertions only on CTest server
assertThat(...);
});
// perform these assertions in all environments
assertThat(...);
}
Werden die Vorannahmen nicht erfüllt,
wird die Assertion nicht ausgeführt.
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("fast")
@Tag("junit5-demo")
class TaggingDemo {
@Test
@Tag("orders")
void testingSpecialCalculation() {
}
}
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<argLine>${surefireArgLine}</argLine>
<properties>
<includeTags>junit5-demo</includeTags>
<excludeTags>orders</excludeTags>
</properties>
</configuration>
</plugin>
@TestInstance(Lifecycle.PER_CLASS)
public class TestLifecycleDemo {
private int index = 0;
@Test
public void test1() throws Exception {
index++;
System.out.println("Ich bin Objekt " + index + " mit ID <" + this + ">");
}
...
}
Standardmäßig ist der Lifecycle wie bei älteren JUnit-Versionen:
Pro Testmethode wird eine neue Instanz der Testklasse erzeugt.
Dies kann nun durch @TestInstance(Lifecycle.PER_CLASS) geändert werden.
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@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
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
Tests können nun in Tests integriert werden;
natürlich als innere Klassen.
Die Klasse
org.junit.jupiter.api.extension.ParameterResolver
ist eine dynamische Schnittstelle, um Tests via Konstruktor oder Methoden, Parameter zu injizieren.
@RepeatedTest(10)
void repeatedTest() {
// wird 10mal wiederholt.
}
...
Tests können mit Wiederholungsangabe definiert werden.
@RepeatedTest(value = 3,
name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("Repeat!")
void customDisplayName(TestInfo testInfo) {
assertEquals(testInfo.getDisplayName(), "Repeat! 1/3");
}
Ausgabe:
Repeat! 1/3
Repeat! 2/3
Repeat! 3/3
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.Assert.assertThat;
public class Arabic2RomanConverterTest {
@ParameterizedTest(name = "{0} konvertiert in \"{1}\"")
@CsvSource({ "0, ''", "1, I", "2, II", "4, IV", "5, V", "9, IX",
"40, XL", "90, XC", "400, CD", "500, D", "900, CM",
"1000, M", "2330, MMCCCXXX", "1984, MCMLXXXIV" })
@DisplayName("Konvertiere arabische Zahl in römische Zeichen")
void testDifferentValues(final int arabicNumber, final String romanChars) throws Exception {
assertThat(new Arabic2RomanConverter().convert(arabicNumber), is(romanChars));
}
}
JUnit Tests können nun einfach parametrisiert werden:
@ParameterizedTest
By Michael Albrecht
A short presentation of the most important features to start