class JUnit5Test {
@Test
void someTest() {
assertTrue(true);
}
}
JUnit 5 is work in progress!
This is based on Milestone 3
(released 30th of November 2016).
Milestone 4 should come out any day now!
Give feedback!
Basics |
Dynamic Tests |
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 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)
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`
}
⇝ Test has parameters!
But where do they come from?
lifecycle works much like before
many details were improved
@Nested
and @DisplayName
make a nice couple
parameter injection
That's all very nice but how is it
Next Generation Testing?
Basics |
Dynamic Tests |
Extensions |
Architecture |
Tools & Setup |
Up to now tests were identified
by names
(test…
in JUnit 3 and before)
by annotations
(@Test
in JUnit 4 and 5)
⇝ Tests had to be known at compile time.
So what?!
What if we want to create tests
for a set of parameters
based on non-source files
with lambdas
⇝ We need to define tests at run time.
Allow creation of tests at run time.
tests are wrapped into DynamicTest
-s
methods that create them
are annotated with @TestFactory
@TestFactory
List<DynamicTest> createPointTests() {
return Arrays.asList(
dynamicTest(
"A Great Test For Point",
() -> { /* test code */ } ),
dynamicTest(
"Another Great Test For Point",
() -> { /* test code */ } )
);
}
The rest is straight-forward:
JUnit detects @TestFactory
methods
calls them to generate tests
adds tests to the test tree
eventually runs them
void pointTest(Point p) { /*...*/ }
@TestFactory
Stream<DynamicText> testingPoints() {
return Stream.of(
/* create points */)
.map(p ->
dynamicTest(
"Testing " + p,
() -> pointTest(p)));
}
void pointTest(Point p) { /*...*/ }
@TestFactory
Stream<DynamicText> testingPoints() {
List<Point> points = asList(
/* create points */);
return DynamicTest.stream(
points,
p -> "Testing " + p,
p -> pointTest(p));
}
void pointTest(Point p) { /*...*/ }
@TestFactory
Stream<DynamicText> testingPoints() {
return Files
.lines(pathToPointFile)
.map(Point::parse)
.map(p -> dynamicTest(
"Testing " + p,
() -> pointTest(p)));
}
with @TestFactory
and DynamicTest
we can create tests at run time:
for sets of parameters
based on external input
as lambdas (yay!)
not fully integrated in lifecycle (yet? #378)
That's all very nice but is this already
Next Generation Testing?
Basics |
Dynamic Tests |
Extensions |
Architecture |
Tools & Setup |
From JUnit 5’s Core Principles:
Prefer extension points over features
Quite literally,
JUnit 5 has Extension Points
Test Instance Post Processor
BeforeAll Callback
Test and Container Execution Condition
BeforeEach Callback
Parameter Resolution
Before Test Execution
After Test Execution
Exception Handling
AfterEach Callback
AfterAll Callback
one interface for each extension point
method arguments capture context
public interface BeforeEachCallback
extends Extension {
void beforeEach(
TestExtensionContext context);
}
an extension can use multiple points
to implement its feature
We want to benchmark our tests!
for each test method
write the elapsed time to console
How?
before test execution: store test launch time
after test execution: print elapsed time
public class BenchmarkExtension implements
BeforeTestExecutionCallback,
AfterTestExecutionCallback {
private long launchTime;
// ...
}
@Override
public void beforeTestExecution(
TestExtensionContext context) {
launchTime = currentTimeMillis();
}
@Override
public void afterTestExecution(
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("Weekend!");
else
return enabled("Fix it!");
}
}
What about parameter injection?
@Test
void someTest(MyServer server) {
// do something with `server`
}
public class MyServerParameterResolver
implements ParameterResolver {
@Override
public boolean supports(ParameterContext p, ...) {
return MyServer.class
== p.getParameter().getType();
}
@Override
public Object resolve(ParameterContext p, ...) {
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 |
Dynamic Tests |
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 for tools to run tests
Separation of concerns V 2.0:
an API to write tests against
a mechanism to discover and run tests
specific engine per variant of tests
(e.g. JUnit 4 or JUnit 5)
orchestration of engines
API between them
an API for tools to run tests
junit-jupiter-api
junit-jupiter-engine
junit-vintage-engine
junit-platform-engine
junit-platform-runner
a lot more
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 in natural language?
⇝ create an engine for it
Once JUnit 5 adoption sets in:
tools are decoupled from implementation details
tools can support all frameworks (almost) 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!
Some community engines:
Other projects:
Docker: starts and stops docker containers
Parameterized: "similar to TestNG DataProvider"
Pioneer: "JUnit 5 Extension Pack"
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 |
Dynamic Tests |
Extensions |
Architecture |
Tools & Setup |
Add this:
org.junit.jupiter
junit-jupiter-api
5.0.0-M3
Have fun!
individual classes:
@RunWith(JUnitPlatform.class)
public class JUnit5Test { ... }
all classes:
@RunWith(JUnitPlatform.class)
@SelectPackages({ "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 launcher:
# run all tests
junit-platform-console \
-cp ${path_to_compiled_test_classes} \
--scan-class-path
# run a specific test
junit-platform-console \
-cp ${path_to_compiled_test_classes} \
--select-class \
${fully_qualified_test_class_name}
you can start writing tests right away
only IntelliJ has native support
running with JUnit 4 is a good compromise
(Read about the setup details.)
new API is an incremental improvement
full of thoughtful details
dynamic tests are very useful
extension model looks very promising
architecture opens up the platform
tool support is not there yet
(Read more about JUnit 5.)
Nicolai Parlog
codefx.org
sitepoint.com/java
@nipafx /
+NicolaiParlog
I’m writing a book!
EA soon ⇝ tiny.cc/fx-weekly
two-day Java 9 course
Zürich, 20.-21.04.
⇝ register at 42talents.com
or hire me!
architecture diagrams: Nicolai Parlog, CC-BY-NC 4.0