class JUnit5Test {
@Test
void someTest() {
assertTrue(true);
}
}
Nicolai Parlog
JUnit 5 is work in progress!
This is based on the alpha version
(released in February 2016).
Some Links:
source /
user guide /
@junitteam
articles on my blog
Give feedback!
Basics |
Extensions |
Architecture |
Tools & Setup |
class JUnit5Test {
@Test
void someTest() {
assertTrue(true);
}
}
⇝ Package visibility suffices!
@BeforeAll
static void beforeAll() { ... }
@BeforeEach
void beforeEach() { ... }
@AfterEach
void afterEach() { ... }
@AfterAll
static void afterAll() { ... }
⇝ Lifecycle annotations have new names.
@Test
@Disabled("Y U No Pass?!")
void failingTest() {
assertTrue(false);
}
⇝ @Ignored
is now @Disabled
.
@Test
@DisabledOnFriday
void failingTest() {
assertTrue(false);
}
⇝ Convenient Extensibility.
But how?
@Test
void someTest() {
...
assertEquals(
expected,
actual,
"Should be equal.");
}
⇝ Failure message comes last.
@Test
void someTest() {
...
assertEquals(
expected,
actual,
() -> "Should " + "be " + "equal.");
}
⇝ Failure message can be created lazily.
@Test
void assertAllProperties() {
Address ad = new Address(
"City", "Street", "42");
assertAll("address",
() -> assertEquals("C", ad.city),
() -> assertEquals("Str", ad.street),
() -> assertEquals("63", ad.number)
);
}
⇝ assertAll
gathers results from multiple assertions
Output if assertAll
fails:
org.opentest4j.MultipleFailuresError:
address (3 failures)
expected: <C> but was: <City>
expected: <Str> but was: <Street>
expected: <63> but was: (42)
void throwing() {
throws new IllegalStateException();
}
@Test
void assertExceptions() {
assertThrows(
Exception.class,
this::throwing);
}
⇝ assertThrows
asserts that
an exception of a specific type was thrown
@Test
void assertExceptions() {
Exception ex = expectThrows(
Exception.class,
this::throwing);
assertEquals("Msg", ex.getMessage());
}
⇝ expectThrows
is like assertThrows
but also returns the exception for further examination
class CountTest {
// lifecycle and tests
@Nested
class CountGreaterZero {
// lifecycle and tests
@Nested
class CountMuchGreaterZero {
// lifecycle and tests
}
}
}
⇝ @Nested
to organize tests in inner classes
@DisplayName("A count")
class CountTest {
@Nested
@DisplayName("when greater zero")
class CountGreaterZero {
@Test
@DisplayName("is positive")
void isPositive() { ... }
}
}
⇝ @DisplayName
to show a nice name
The effects of @Nested
and @DisplayName
:
@Test
void someTest(MyServer server) {
// do something with `server`
}
⇝ Parameter Injection!
But where does it come from?
lifecycle works much like before
many details were improved
@Nested
and @DisplayName
make a nice couple
parameter injection
no lambdas (so far)
That's all very nice but how is it
Next Generation Testing?
Basics |
Extensions |
Architecture |
Tools & Setup |
Manage a test’s full lifecycle.
@RunWith(MockitoJUnitRunner.class)
public class MyTest { ... }
very flexible
heavyweight
exclusive
Execute code before and after statements.
public class MyTest {
@Rule
public MockitoRule rule =
MockitoJUnit.rule();
}
added in 4.7
lightweight
limited to before/after behavior
Extension model is not optimal:
two competing mechanisms
each with limitations
but with considerable overlap
composition can cause problems
From JUnit 5’s Core Principles:
Prefer extension points over features
Quite literally JUnit 5 has Extension Points
BeforeAll Callback
Test Instance Post Processing
Conditional Test Execution
BeforeEach Callback
Parameter Resolution
Exception Handling
AfterEach Callback
AfterAll Callback
one interface for each extension point
method arguments capture context
public interface BeforeEachExtensionPoint
extends ExtensionPoint {
void beforeEach(
TestExtensionContext context)
throws Exception;
}
an extension can use multiple points
to implement its feature
We want to benchmark our tests!
for each test method
write the runtime to console
How?
store test launch time before each test
compute runtime and print after each test
public class BenchmarkExtension implements
BeforeEachExtensionPoint,
AfterEachExtensionPoint {
private long launchTime;
// ...
}
@Override
public void beforeEach(
TestExtensionContext context) {
launchTime = currentTimeMillis();
}
@Override
public void afterEach(
TestExtensionContext context) {
printf("Test '%s' took %d ms.%n",
context.getDisplayName(),
currentTimeMillis() - launchTime);
}
Remember This?
@Test
@DisabledOnFriday
void failingTest() {
assertTrue(false);
}
Let’s see how it works!
public class DisabledOnFridayCondition
implements TestExecutionCondition {
@Override
public ConditionEvaluationResult evaluate(
TestExtensionContext context) {
if (isFriday())
return disabled("Happy Weekend!");
else
return enabled("Fix it!");
}
}
What about parameter injection?
@Test
void someTest(MyServer server) {
// do something with `server`
}
public class MyServerParameterResolver
implements MethodParameterResolver {
@Override
public boolean supports(
Parameter parameter, ... ) {
return parameter.getType()
== MyServer.class;
}
@Override
public Object resolve( ... ) {
return new MyServer();
}
}
How do we apply extensions?
@ExtendWith(DisabledOnFridayCondition.class)
class JUnit5Test {
...
}
That’s technical and verbose… :(
Meta-annotations to the rescue!
JUnit 5’s annotations are meta-annotations
JUnit 5 checks recursively for annotations
⇝ we can create our own annotations!
@ExtendWith(DisabledOnFridayCondition.class)
public @interface DisabledOnFriday { }
@Test
@Tag("integration")
@ExtendWith(BenchmarkExtension.class)
@ExtendWith(MyServerParameterResolver.class)
public @interface IntegrationTest { }
@IntegrationTest
@DisabledOnFriday
void testLogin(MyServer server) { ... }
flexibility because of many extension points
extensions compose well
customizable due to meta-annotations
(We left out some details.)
That's all very nice but how is it
Next Generation Testing?
Basics |
Extensions |
Architecture |
Tools & Setup |
a single JAR (ignoring Hamcrest)
used by
developers
extensions
IDEs, build-tools
no separation of concerns
tools provide us with awesome features!
but API is not powerful enough
I know, I’ll use reflection!
nothing was safe!
bound tools to implementation details
made maintenance and evolution very hard
Part of JUnit’s success is its great tool support!
But the same tools locked development in.
The success of JUnit as a platform prevents the development of JUnit as a tool.
(Johannes Link)
Separation of concerns:
an API to write tests against
a mechanism to discover and run tests
An API to write test against.
Ok.
A mechanism to discover and run tests.
Which tests?
JUnit (obviously!)
But which version?
Eh, 5?
Just lame old @Test-annotated?
Err...
No extension for lambdas?!
Ok, ok!
Separation of concerns V 2.0:
an API to write tests against
a mechanism to discover and run tests
specific mechanism per variant of tests
(e.g. JUnit 4 or JUnit 5)
orchestration of specific mechanisms
API between them
junit5-api
the API for us to write tests against
junit-enginge-api
the API all test engines have to implement
junit5-engine
implementation for JUnit 5 tests
junit-launcher
discovers test engines
orchestrates their execution
provides an API to IDEs and build tools
clear separation of concerns
API for developers
API for tools
That's all very nice but how is it
Next Generation Testing?
Because it opens up the platform!
want to run JUnit 4 tests?
⇝ create an engine for it
want TestNG to have support like JUnit?
⇝ create an engine for it
want to write tests as lambdas?
⇝ create an engine for it
Once JUnit 5 adoption sets in:
tools are decoupled from implementation details
tools can support all test frameworks equally well
new frameworks start with full tool support
developers can try out new things
A new generation of test frameworks might arise!
JUnit’s success as a platform
becomes available to everybody.
This heralds the
next generation of testing on the JVM!
clear separation of concerns:
APIs for developers, tools,
and new frameworks
opens up the platform
tool support for everybody!
(There’s even more to the story.)
Basics |
Extensions |
Architecture |
Tools & Setup |
Add this:
org.junit
/ junit5-api
/ 5.0.0-ALPHA
Have fun!
individual classes:
@RunWith(JUnit5.class)
public class JUnit5Test { ... }
all classes:
@RunWith(JUnit5.class)
@Packages({ "my.test.package" })
public class JUnit5TestSuite { }
JUnit 5 team provides rudimentary
Gradle plugin and Maven Surefire provider
(see User Guide for details)
There is a console runner:
# run all tests
junit-console
-p ${path_to_compiled_test_classes}
-a
# run a specific test
junit-console
-p ${path_to_compiled_test_classes}
org.codefx.demo.junit5.HelloWorldTest
you can start writing tests right away
tools have no native support yet
running with JUnit 4 is a good compromise
(Read about the setup details.)
new API is an incremental improvement
full of thoughtful details
extension model looks very promising
architecture opens up the platform
tool support is not there yet
(Read more about JUnit 5.)
you can hire me
since 2014: Java developer at Disy
2011-2014: Java developer at Fraunhofer ISI
until 2010: CS and Math at TU Dortmund
bubbles: Keith Williamson (CC-BY 2.0)
architecture diagrams:
Nicolai Parlog
(CC-BY-NC 4.0)
question-mark: Milos Milosevic (CC-BY 2.0)