One of the prominent goals of JUnit 5 is to make the interface between JUnit and its programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to decouple the internals of discovering and executing tests from all the filtering and configuration that’s necessary from the outside.
JUnit 5 introduces the concept of a Launcher
that can be used to discover, filter, and
execute tests. Moreover, third party test libraries – like Spock, Cucumber, and FitNesse
– can plug into the JUnit Platform’s launching infrastructure by providing a custom
{TestEngine}
.
The launching API is in the {junit-platform-launcher}
module.
An example consumer of the launching API is the {ConsoleLauncher}
in the
{junit-platform-console}
project.
Introducing test discovery as a dedicated feature of the platform itself will (hopefully) free IDEs and build tools from most of the difficulties they had to go through to identify test classes and test methods in the past.
Usage Example:
link:{testDir}/example/UsingTheLauncherDemo.java[role=include]
link:{testDir}/example/UsingTheLauncherDemo.java[role=include]
There’s currently the possibility to select classes, methods, and all classes in a package or even search for all tests in the classpath. Discovery takes place across all participating test engines.
The resulting TestPlan
is a hierarchical (and read-only) description of all engines,
classes, and test methods that fit the LauncherDiscoveryRequest
. The client can
traverse the tree, retrieve details about a node, and get a link to the original source
(like class, method, or file position). Every node in the test plan has a unique ID
that can be used to invoke a particular test or group of tests.
To execute tests, clients can use the same LauncherDiscoveryRequest
as in the discovery
phase or create a new request. Test progress and reporting can be achieved by registering
one or more {TestExecutionListener}
implementations with the Launcher
as in the
following example.
link:{testDir}/example/UsingTheLauncherDemo.java[role=include]
There is no return value for the execute()
method, but you can easily use a listener to
aggregate the final results in an object of your own. For an example see the
{SummaryGeneratingListener}
.
JUnit currently provides two {TestEngine}
implementations out of the box:
-
{junit-jupiter-engine}
: The core of JUnit Jupiter. -
{junit-vintage-engine}
: A thin layer on top of JUnit 4 to allow running vintage tests with the launcher infrastructure.
Third parties may also contribute their own TestEngine
by implementing the interfaces
in the {junit-platform-engine} module and registering their engine. Engine registration
is currently supported via Java’s java.util.ServiceLoader
mechanism. For example, the
junit-jupiter-engine
module registers its org.junit.jupiter.engine.JupiterTestEngine
in a file named org.junit.platform.engine.TestEngine
within the /META-INF/services
in
the junit-jupiter-engine
JAR.
Note
|
{HierarchicalTestEngine} is a convenient abstract base implementation (used by
the {junit-jupiter-engine} ) that only requires implementors to provide the logic for
test discovery. It implements execution of TestDescriptors that implement the Node
interface, including support for parallel execution.
|
In addition to the public {Launcher}
API method for registering test execution
listeners programmatically, custom {TestExecutionListener}
implementations discovered
at runtime via Java’s java.util.ServiceLoader
facility are automatically registered
with the DefaultLauncher
. For example, an example.TestInfoPrinter
class
implementing {TestExecutionListener}
and declared within the
/META-INF/services/org.junit.platform.launcher.TestExecutionListener
file is loaded
and registered automatically.