Needless to say, the latest major version of Java's testing framework as of 2019.
> gradle --version
------------------------------------------------------------
Gradle 5.6.2
------------------------------------------------------------
Build time: 2019-09-05 16:13:54 UTC
Revision: 55a5e53d855db8fc7b0e494412fc624051a8e781
Kotlin: 1.3.41
Groovy: 2.5.4
Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM: 11.0.4 (AdoptOpenJDK 11.0.4+11)
OS: Windows 10 10.0 amd64
Hello World
build.gradle
plugins {
id "java"
}
sourceCompatibility = 11
targetCompatibility = 11
[compileJava, compileTestJava]*.options*.encoding = "UTF-8"
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter:5.5.2"
}
--If you want to start using JUnit5 for the time being, specify ʻorg.junit.jupiter: junit-jupiter` as the dependency ( details below ).
file organization
|-build.gradle
`-src/test/java/
`-sample/junit5/
|-JUnit5Test.java
|-JUnit5Tests.java
|-TestJUnit5.java
`-Hoge.java
――We have 4 types of test classes
JUnit5Test.java
package sample.junit5;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
class JUnit5Test {
@Test
void fail() {
Assertions.assertEquals(10, 8);
}
static class StaticClass {
@Test
void fail() {
Assertions.assertEquals(10, 8);
}
}
static class StaticTest {
@Test
void fail() {
Assertions.assertEquals(10, 8);
}
}
class InnerTest {
@Test
void fail() {
Assertions.assertEquals(10, 8);
}
}
}
--Each class defines only the fail ()
method that always fails
--Two static nested classes, StaticClass
and StaticTest
,
Defining the ʻInnerTestclass as an inner class --The above example is
JUnit5Test.java, but the remaining three (
JUnit5Tests.java,
TestJUnit5.java,
Hoge.java`) have the same implementation.
There is a tool called ** ConsoleLauncher ** for running JUnit5 on the command line. The substance is a jar file in which each module of JUnit5 is consolidated into one. Maven Central Repository To download.
Here, download junit-platform-console-standalone-1.5.2.jar
and verify it.
#Compile
> gradle compileTestJava
#Run
> java -jar junit-platform-console-standalone-1.5.2.jar ^
-cp build\classes\java\test ^
--scan-classpath build\classes\java\test
...
Failures (8):
JUnit Jupiter:Hoge$StaticTest:fail()
MethodSource [className = 'sample.junit5.Hoge$StaticTest', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:TestJUnit5:fail()
MethodSource [className = 'sample.junit5.TestJUnit5', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:TestJUnit5$StaticTest:fail()
MethodSource [className = 'sample.junit5.TestJUnit5$StaticTest', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:JUnit5Test:fail()
MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:JUnit5Tests:fail()
MethodSource [className = 'sample.junit5.JUnit5Tests', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:TestJUnit5$StaticClass:fail()
MethodSource [className = 'sample.junit5.TestJUnit5$StaticClass', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:JUnit5Test$StaticTest:fail()
MethodSource [className = 'sample.junit5.JUnit5Test$StaticTest', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
JUnit Jupiter:JUnit5Tests$StaticTest:fail()
MethodSource [className = 'sample.junit5.JUnit5Tests$StaticTest', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
Test run finished after 91 ms
[ 10 containers found ]
[ 0 containers skipped ]
[ 10 containers started ]
[ 0 containers aborted ]
[ 10 containers successful ]
[ 0 containers failed ]
[ 8 tests found ]
[ 0 tests skipped ]
[ 8 tests started ]
[ 0 tests aborted ]
[ 0 tests successful ]
[ 8 tests failed ]
--Add the dependency (other than JUnit5) used in the test to the classpath with the -cp
option.
--JUnit5 classes are in junit-platform-console-standalone-1.5.2.jar
----scan-classpath
to specify where to find the test class you want to run
--Test results are output to the console
> java -jar junit-platform-console-standalone-1.5.2.jar --help
...
-n, --include-classname=PATTERN
Provide a regular expression to include only classes whose fully
qualified names match. To avoid loading classes unnecessarily,
the default pattern only includes class names that begin with
"Test" or end with "Test" or "Tests". When this option is
repeated, all patterns will be combined using OR semantics.
Default: [^(Test.*|.+[.$]Test.*|.*Tests?)$]
...
--The test class executed in the above example is as follows
- Hoge$StaticTest
- JUnit5Test
- JUnit5Test$StaticTest
- JUnit5Tests
- JUnit5Tests$StaticTest
- TestJUnit5
- TestJUnit5$StaticClass
- TestJUnit5$StaticTest
--On the contrary, the test of the next class has not been executed.
- Hoge
- Hoge$InnerTest
- Hoge$StaticClass
- JUnit5Test$InnerTest
- JUnit5Test$StaticClass
- JUnit5Tests$InnerTest
- JUnit5Tests$StaticClass
- TestJUnit5$InnerTest
--Specify the test class search condition with the -n
or --include-classname
option (regular expression specification)
-The default is^(Test.*|.+[.$]Test.*|.*Tests?)$
-Result confirmed by REGEXPER
--Therefore, classes that start with Test
or end with Test
or Tests
are targeted (including static nested classes).
--Inner classes are not covered ( later @Nested
is required)
--The test class does not have to be public
But I think it's usually done from the build tool you're using. Gradle has native support for running JUnit5 since 4.6, so try running it from Gradle.
build.gradle
...
test {
useJUnitPlatform() //★ Addition
}
--Gradle also supports other testing frameworks such as JUni4 and TestNG
--If you use JUnit5, you need to explicitly declare that you will use JUnit5.
--The setting for that is test {useJUnitPlatform ()}
Run the test
> gradle test
...
> Task :test FAILED
sample.junit5.Hoge > fail() FAILED
org.opentest4j.AssertionFailedError at Hoge.java:10
sample.junit5.JUnit5Test > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Test.java:10
sample.junit5.JUnit5Tests$StaticTest > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Tests.java:23
sample.junit5.JUnit5Tests > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Tests.java:10
sample.junit5.TestJUnit5 > fail() FAILED
org.opentest4j.AssertionFailedError at TestJUnit5.java:10
sample.junit5.Hoge$StaticClass > fail() FAILED
org.opentest4j.AssertionFailedError at Hoge.java:16
sample.junit5.Hoge$StaticTest > fail() FAILED
org.opentest4j.AssertionFailedError at Hoge.java:23
sample.junit5.JUnit5Test$StaticClass > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Test.java:16
sample.junit5.JUnit5Test$StaticTest > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Test.java:23
sample.junit5.JUnit5Tests$StaticClass > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Tests.java:16
sample.junit5.TestJUnit5$StaticClass > fail() FAILED
org.opentest4j.AssertionFailedError at TestJUnit5.java:16
sample.junit5.TestJUnit5$StaticTest > fail() FAILED
org.opentest4j.AssertionFailedError at TestJUnit5.java:23
12 tests completed, 12 failed
...
BUILD FAILED in 6s
2 actionable tasks: 1 executed, 1 up-to-date
--The following classes were executed
- Hoge
*
- Hoge$StaticClass
*
- Hoge$StaticTest
- JUnit5Test
- JUnit5Test$StaticClass
*
- JUnit5Test$StaticTest
- JUnit5Tests
- JUnit5Tests$StaticClass
*
- JUnit5Tests$StaticTest
- TestJUnit5
- TestJUnit5$StaticClass
- TestJUnit5$StaticTest
--More classes are executed than when executed with ConsoleLauncher (more classes are marked with *)
--If you run the test from Gradle, the default is the testClassesDirs property of the Test task (https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle All classes existing in the location specified by .api.tasks.testing.Test:testClassesDirs) are targeted.
--As the documentation says, the default value is set to the destination directory of sourceSets.test
by the Java plugin.
--In other words, all the classes under src / test / java
are targeted (whether it starts with Test
, etc.)
--If you want to narrow down the target, [includes](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing. Test: includes) and [excludes](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test: excludes )
--However, the inner class (ʻInnerTest`) is excluded by default, which is the same as ConsoleLauncher.
The confirmed version is 2019-09 Standard Edition
of Pleiades.
To execute it, right-click the src / test / java
folder and select" Run "->" JUnit Test ".
Hoge
and StaticClass
were also targeted.
The confirmed version is the Community version 2019.2.3.
For IDEA, if you open it as a Gradle project, Gradle's test
task will run the test.
So, the behavior in that case is the same as when running from Gradle.
You can also specify the execution configuration from [Run / Debug Configurations] without using Gradle. However, in that case, it depends on the specification of "Test kind".
JUnit5 consists of three major modules (subprojects).
JUnit Platform
The basis for running the test framework on the JVM. It provides a mechanism to execute a module that implements an interface called TestEngine.
It also provides ConsoleLauncher
etc. for launching from the console.
JUnit Jupiter
A module for creating and running JUnit 5 tests.
Provides JupiterTestEngine that implements TestEngine
for JUnit 5.
You can think of Jupiter as JUnit 5.
JUnit Vintage
A class that implements TestEngine
to run JUnit 3 and 4 on the JUnit Platform- [VintageTestEngine](https://junit.org/junit5/docs/current/api/org/junit/vintage/engine/VintageTestEngine. A module that provides html).
I think it was prepared for compatibility during the transition period, so it is not necessary if you are introducing new JUnit 5.
JUnit 5 consists of the following two modules.
--Platform for running the test framework --Specific test framework module (Jupiter, Vintage)
The Platform is needed to run the tests. Jupiter is needed when you want to write tests in JUnit 5, and Vintage is needed when you want to run JUnit 4 on the JUnit Platform.
Modules such as Platform and Jupiter correspond to the Group in Maven's classification.
Within each group, there are several more Artifacts.
** Artifact dependencies within each module **
When you actually write your JUnit 5 tests, you need to select just the right ones from these Artifacts and add them to your dependencies. As you can see from the figure above, it is difficult at first glance to determine which one is needed.
So, in 5.4.0, an Artifact called junit-jupiter was added to the ʻorg.junit.jupiter` Group.
** Figure with junit-jupiter added **
junit-jupiter
is an Artifact that summarizes only the minimum dependencies needed to write tests in JUnit 5.
So if you just want to write tests in JUnit 5, you only need to add one of these Artifacts to your dependencies.
package sample.junit5;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
void success() {
Assertions.assertEquals(10, 10);
}
@Test
void fail() {
Assertions.assertEquals(10, 8);
}
}
-The method annotated with @Test becomes the test method. --The package is different from org.junit.Test up to JUnit4. --It seems to be able to mix both 4 and 5 tests during the transition period to 4-> 5.
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@BeforeAll
static void beforeAll() {
System.out.println("JUnit5Test#beforeAll()");
}
@BeforeEach
void beforeEach() {
System.out.println(" JUnit5Test#beforeEach()");
}
@Test
void test1() {
System.out.println(" JUnit5Test#test1()");
}
@Test
void test2() {
System.out.println(" JUnit5Test#test2()");
}
@AfterEach
void afterEach() {
System.out.println(" JUnit5Test#afterEach()");
}
@AfterAll
static void afterAll() {
System.out.println("JUnit5Test#afterAll()");
}
}
Execution result
JUnit5Test#beforeAll()
JUnit5Test#beforeEach()
JUnit5Test#test1()
JUnit5Test#afterEach()
JUnit5Test#beforeEach()
JUnit5Test#test2()
JUnit5Test#afterEach()
JUnit5Test#afterAll()
-Methods with @BeforeAll are only once at the very beginning in the test class. To be executed
--Method must be static
-Methods with @BeforeEach are executed before each test method.
-Methods with @AfterAll are only once at the very end of the test class. To be executed
--Method must be static
Methods with @AfterEach will be executed after each test method
package sample.junit5;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
@DisplayName("In class")
class JUnit5Test {
@Test
@DisplayName("To succeed")
void success() {
Assertions.assertEquals(10, 10);
}
@Test
@DisplayName("In failure")
void fail() {
Assertions.assertEquals(10, 8);
}
}
** When run with ConsoleLauncher **
> java -jar junit-platform-console-standalone-1.5.2.jar ^
-cp build\classes\java\test ^
--scan-classpath build\classes\java\test
...
.
+-- JUnit Jupiter [OK]
| '--In class[OK]
| +--To succeed[OK]
| '--In failure[X] expected: <10> but was: <8>
'-- JUnit Vintage [OK]
Failures (1):
JUnit Jupiter:In class:In failure
MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'fail', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
[...]
Test run finished after 87 ms
[ 3 containers found ]
[ 0 containers skipped ]
[ 3 containers started ]
[ 0 containers aborted ]
[ 3 containers successful ]
[ 0 containers failed ]
[ 2 tests found ]
[ 0 tests skipped ]
[ 2 tests started ]
[ 0 tests aborted ]
[ 1 tests successful ]
[ 1 tests failed ]
** When running on Gradle **
> gradle test
...
> Task :test FAILED
sample.junit5.JUnit5Test > fail() FAILED
org.opentest4j.AssertionFailedError at JUnit5Test.java:18
2 tests completed, 1 failed
FAILURE: Build failed with an exception.
...
BUILD FAILED in 6s
2 actionable tasks: 1 executed, 1 up-to-date
** HTML report **
** XML report **
xml:TEST-sample.junit5.JUnit5Test.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="sample.junit5.JUnit5Test" tests="2" skipped="0" failures="1" errors="0" timestamp="2019-10-08T13:55:57" hostname="niconico" time="0.022">
<properties/>
<testcase name="success()" classname="sample.junit5.JUnit5Test" time="0.016"/>
<testcase name="fail()" classname="sample.junit5.JUnit5Test" time="0.005">
<failure message="org.opentest4j.AssertionFailedError: expected: <10> but was: <8>" type="org.opentest4j.AssertionFailedError">org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
...
--By adding @DisplayName
to a class or method, you can specify the name of the test as an arbitrary string.
――However, Gradle's report seems to be apt to correspond
package sample.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@BeforeEach
void beforeEach() {
System.out.println("JUnit5Test.beforeEach()");
}
@Test
void test1() {
System.out.println(" JUnit5Test.test1()");
}
@Test
void test2() {
System.out.println(" JUnit5Test.test2()");
}
@AfterEach
void afterEach() {
System.out.println("JUnit5Test.afterEach()");
}
@Nested
class NestedTest {
@BeforeEach
void beforeEach() {
System.out.println(" NestedTest.beforeEach()");
}
@Test
void test1() {
System.out.println(" NestedTest.test1()");
}
@Test
void test2() {
System.out.println(" NestedTest.test2()");
}
@AfterEach
void afterEach() {
System.out.println(" NestedTest.afterEach()");
}
}
}
Execution result
JUnit5Test.beforeEach()
JUnit5Test.test1()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
JUnit5Test.test2()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
NestedTest.beforeEach()
NestedTest.test1()
NestedTest.afterEach()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
NestedTest.beforeEach()
NestedTest.test2()
NestedTest.afterEach()
JUnit5Test.afterEach()
--Annotate a non-static
inner class with @Nested and nest test classes Can be
--Because it must be non-static
, @BeforeAll
and @AfterAll
cannot be specified as they are [^ 2]
--If you really want to specify it, you need to set PER_CLASS
by specifying test instance lifecycle below.
[^ 2]: @BeforeAll
, @ AfterAll
must be specified as static
methods, but non- static
inner classes cannot define static
methods according to the Java language specification.
package sample.junit5;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
void test1() {
Assumptions.assumeTrue(true);
System.out.println("test1()");
}
@Test
void test2() {
Assumptions.assumeTrue(false);
System.out.println("test2()");
}
@Test
void test3() {
Assumptions.assumingThat(true, () -> {
System.out.println("test3() assumption.");
});
System.out.println("test3()");
}
@Test
void test4() {
Assumptions.assumingThat(false, () -> {
System.out.println("test4() assumption.");
});
System.out.println("test4()");
}
}
Execution result
test1()
test3() assumption.
test3()
test4()
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- test1() [OK]
+-- test2() [A] Assumption failed: assumption is not true
+-- test3() [OK]
'-- test4() [OK]
Test run finished after 84 ms
[ 2 containers found ]
[ 0 containers skipped ]
[ 2 containers started ]
[ 0 containers aborted ]
[ 2 containers successful ]
[ 0 containers failed ]
[ 4 tests found ]
[ 0 tests skipped ]
[ 4 tests started ]
[ 1 tests aborted ]
[ 3 tests successful ]
[ 0 tests failed ]
-If you use Assumptions.assumeTrue (boolean), the argument is Subsequent tests are run only when you pass true
--If it is false
, the rest of the processing in the test method will be interrupted.
--Aborted test is neither successful nor failed, but aborted
-Assumptions.assumingThat (boolean, Executable), the process passed in the second argument will be executed only when the value of the first argument is true
.
package sample.junit5;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
void test1() {
System.out.println("test1()");
}
@Test
@Disabled
void test2() {
System.out.println("test2()");
}
}
Execution result
test1()
-Test methods with @Disabled will no longer be executed --Can be attached to the class (in that case, all test methods in the test class will not be executed)
OS
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
class JUnit5Test {
@Test
@EnabledOnOs(OS.WINDOWS)
void test1() {
System.out.println("enabled on windows");
}
@Test
@EnabledOnOs(OS.MAC)
void test2() {
System.out.println("enabled on mac");
}
@Test
@DisabledOnOs(OS.WINDOWS)
void test3() {
System.out.println("disabled on windows");
}
@Test
@DisabledOnOs(OS.MAC)
void test4() {
System.out.println("disabled on mac");
}
}
Execution result
enabled on windows
disabled on mac
-With @EnabledOnOs, you can enable the test only on a specific OS.
-With @DisabledOnOs you can disable the test only on certain OS
--For value
, specify the constant defined in OS.
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;
class JUnit5Test {
@Test
@EnabledOnJre(JRE.JAVA_11)
void test1() {
System.out.println("enabled on java 11");
}
@Test
@EnabledOnJre(JRE.JAVA_12)
void test2() {
System.out.println("enabled on java 12");
}
@Test
@DisabledOnJre(JRE.JAVA_11)
void test3() {
System.out.println("disabled on java 11");
}
@Test
@DisabledOnJre(JRE.JAVA_12)
void test4() {
System.out.println("disabled on java 12");
}
}
Execution result
enabled on java 11
disabled on java 12
-Add @EnabledOnJre to enable testing only on certain Java versions it can
-@DisabledOnJre disables testing only on certain Java versions it can
--For value
, specify the constant defined in JRE.
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
class JUnit5Test {
@Test
@EnabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK")
void test1() {
System.out.println("enabled if AdoptOpenJDK");
}
@Test
@EnabledIfSystemProperty(named = "java.vendor", matches = "Oracle.*")
void test2() {
System.out.println("enabled if Oracle");
}
@Test
@DisabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK")
void test3() {
System.out.println("disabled if AdoptOpenJDK");
}
@Test
@DisabledIfSystemProperty(named = "java.vendor", matches = "Oracle.*")
void test4() {
System.out.println("disabled if Oracle");
}
}
Execution result
enabled if AdoptOpenJDK
disabled if Oracle
-If @EnabledIfSystemProperty is added, the test will be performed based on the value of the system property. Can be enabled
-If @DisabledIfSystemProperty is added, the test will be performed based on the value of the system property. Can be disabled
--Specify the name of the system property you want to condition in named
--Specify the conditional value in matches
with a regular expression (whole match)
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
class JUnit5Test {
@Test
@EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\AdoptOpenJDK\\\\.*")
void test1() {
System.out.println("enabled if AdoptOpenJDK");
}
@Test
@EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\OpenJDK\\\\.*")
void test2() {
System.out.println("enabled if OpenJDK");
}
@Test
@DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\AdoptOpenJDK\\\\.*")
void test3() {
System.out.println("disabled if AdoptOpenJDK");
}
@Test
@DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\OpenJDK\\\\.*")
void test4() {
System.out.println("disabled if OpenJDK");
}
}
Execution result
enabled if AdoptOpenJDK
disabled if OpenJDK
-If @EnabledIfEnvironmentVariable is added, the test will be performed based on the value of the environment variable. Can be enabled
-If @DisabledIfEnvironmentVariable is added, the test will be performed based on the value of the environment variable. Can be disabled
--Specify the name of the environment variable you want to condition in named
--Specify the conditional value in matches
with a regular expression (whole match)
package sample.junit5;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
@Tag("foo")
@Tag("fizz")
void test1() {
System.out.println("test1@(foo, fizz)");
}
@Test
@Tag("bar")
@Tag("fizz")
void test2() {
System.out.println("test2@(bar, fizz)");
}
@Test
@Tag("fizz")
void test3() {
System.out.println("test3@(fizz)");
}
@Test
@Tag("buzz")
void test4() {
System.out.println("test4@(buzz)");
}
}
** When executed normally **
Execution result
> java -jar junit-platform-console-standalone-1.5.2.jar ^
-cp build\classes\java\test ^
--scan-classpath build\classes\java\test ^
-e junit-jupiter
...
test1@(foo, fizz)
test2@(bar, fizz)
test3@(fizz)
test4@(buzz)
**-When narrowing down by include-tag **
Execution result
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "fizz"
...
test1@(foo, fizz)
test2@(bar, fizz)
test3@(fizz)
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "foo & fizz"
...
test1@(foo, fizz)
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "foo | bar"
...
test1@(foo, fizz)
test2@(bar, fizz)
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "!foo & fizz"
...
test2@(bar, fizz)
test3@(fizz)
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "foo | !fizz"
...
test1@(foo, fizz)
test4@(buzz)
-You can tag test cases by adding @Tag to test classes and methods.
--The character string specified in the tag name must meet the following conditions:
--Not null
or empty string
--Does not contain whitespace
--Does not include control characters
--Does not include the following reserved characters
- (
- )
- ,
- &
- |
- !
--Tags can be filtered according to the conditions specified at runtime
--For ConsoleLauncher
----include-tag
can only target tags that match the conditions
----exclude-tag
can only target tags that do not match the conditions
--When executing from Gradle, specify as follows
build.gradle
...
test {
useJUnitPlatform {
includeTags "foo | !fizz"
}
}
-[includeTags](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/junitplatform/JUnitPlatformOptions.html#includeTags-java.lang.String ...-) or [excludeTags ](Https: //docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/junitplatform/JUnitPlatformOptions.html#excludeTags-java.lang.String ...-)
--Conditions for narrowing down tags can be described with a dedicated ** tag expression **
--Tag expressions can describe complex conditions using operators
- !
:NOT
- &
:AND
- |
:OR
--It is also possible to summarize the conditions with parentheses ()
Execution result
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "(!foo & fizz) | buzz"
...
test2@(bar, fizz)
test3@(fizz)
test4@(buzz)
package sample.junit5;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
void bear() {
System.out.println("bear");
}
@Test
void ant() {
System.out.println("ant");
}
@Test
void cat() {
System.out.println("cat");
}
@Test
void dog() {
System.out.println("dog");
}
}
Execution result
ant
cat
dog
bear
By default, the execution order of test methods is deliberately determined by a non-trivial algorithm [^ 1]. This is because it is desirable that unit tests do not depend on the execution order.
[^ 1]: In short, I think it's an order that isn't random but can't be guessed.
However, when it comes to integration and functional testing, the order of execution can be important. [^ 8]
[^ 8]: After registering as a master, move individual functions (probably)
At such times, a mechanism is provided to control the test execution order.
package sample.junit5;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
class JUnit5Test {
@Test
void bear() {
System.out.println("bear");
}
@Test
void ant() {
System.out.println("ant");
}
@Test
void cat() {
System.out.println("cat");
}
@Test
void dog() {
System.out.println("dog");
}
}
Execution result
ant
bear
cat
dog
--Set annotation @TestMethodOrder in test class
--For value
, specify the Class
object of the class that implements MethodOrderer. To do
--MethodOrder
provides the ability to control the order in which methods are executed.
--There are three standard implementations of MethodOrder
.
-Alphanumeric has the method name String.compareTo (String) Compare and sort by: //docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/String.html#compareTo (java.lang.String))
--If the method names are the same, compare the ones expressed as strings including the arguments.
package sample.junit5;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class JUnit5Test {
@Test
@Order(3)
void bear() {
System.out.println("bear");
}
@Test
@Order(4)
void ant() {
System.out.println("ant");
}
@Test
@Order(2)
void cat() {
System.out.println("cat");
}
@Test
@Order(1)
void dog() {
System.out.println("dog");
}
}
Execution result
dog
cat
bear
ant
-Using OrderAnnotation, [@Order](https://junit. org / junit5 / docs / current / api / org / junit / jupiter / api / Order.html) You can specify the order with annotations.
--Methods that do not specify @ Order
are assigned ʻInteger.MAX_VALUE` by default.
package sample.junit5;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.Random.class)
class JUnit5Test {
@Test
void bear() {
System.out.println("bear");
}
@Test
void ant() {
System.out.println("ant");
}
@Test
void cat() {
System.out.println("cat");
}
@Test
void dog() {
System.out.println("dog");
}
}
Execution result
#1st time
ant
dog
cat
bear
#Second time
bear
ant
cat
dog
-If you specify Random, the method execution order will be random.
package sample.junit5;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@BeforeEach
void before() {
System.out.println("before@" + this.hashCode());
}
@Test
void test1() {
System.out.println("test1@" + this.hashCode());
}
@Test
void test2() {
System.out.println("test2@" + this.hashCode());
}
@Test
void test3() {
System.out.println("test3@" + this.hashCode());
}
}
Execution result
before@278240974
test1@278240974
before@370370379
test2@370370379
before@671046933
test3@671046933
--By default, a new test class instance is created each time the test method is executed.
package sample.junit5;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // ★
class JUnit5Test {
@BeforeEach
void before() {
System.out.println("before@" + this.hashCode());
}
@Test
void test1() {
System.out.println("test1@" + this.hashCode());
}
@Test
void test2() {
System.out.println("test2@" + this.hashCode());
}
@Test
void test3() {
System.out.println("test3@" + this.hashCode());
}
}
Execution result
before@1504642150
test1@1504642150
before@1504642150
test2@1504642150
before@1504642150
test3@1504642150
--Annotate the test class with @TestInstance and set value
to [PER_CLASS]( Specify https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestInstance.Lifecycle.html#PER_CLASS)
--Then, a test instance will be created for each test class.
--Therefore, all the test methods in the same test class are executed in the same instance.
--If the life cycle is set to PER_CLASS
, @BeforeAll
and @AfterAll
can be set in the instance method.
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Test {
@BeforeAll
static void staticBeforeAll() {
System.out.println("staticBeforeAll()");
}
@BeforeAll
void beforeAll() {
System.out.println("beforeAll()");
}
@Test
void test() {
System.out.println("test()");
}
@AfterAll
void afterAll() {
System.out.println("afterAll()");
}
@AfterAll
static void staticAfterAll() {
System.out.println("staticAfterAll()");
}
}
Execution result
beforeAll()
staticBeforeAll()
test()
staticAfterAll()
afterAll()
--It is also possible to leave it attached to the static
method
--The advantage of this is that you can add @BeforeAll
and @AfterAll
to test classes nested with @ Nested
.
--If you do not specify PER_CLASS
, @BeforeAll
, @AfterAll
must make the method static
.
--However, since the class with @ Nested
is an inner class, the static
method cannot be defined due to the Java language specification.
--Therefore, I couldn't define the process I want to execute first and last only in the @ Nested
class.
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
class JUnit5Test {
@BeforeAll
static void beforeAll() {
System.out.println("JUnit5Test.beforeAll()");
}
@BeforeEach
void beforeEach() {
System.out.println(" JUnit5Test.beforeEach()");
}
@Test
void test1() {
System.out.println(" JUnit5Test.test1()");
}
@Test
void test2() {
System.out.println(" JUnit5Test.test2()");
}
@AfterEach
void afterEach() {
System.out.println(" JUnit5Test.afterEach()");
}
@AfterAll
static void afterAll() {
System.out.println("JUnit5Test.afterAll()");
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class NestedTest {
@BeforeAll
void beforeAll() {
System.out.println(" NestedTest.beforeAll() *");
}
@BeforeEach
void beforeEach() {
System.out.println(" NestedTest.beforeEach()");
}
@Test
void test1() {
System.out.println(" NestedTest.test1()");
}
@Test
void test2() {
System.out.println(" NestedTest.test2()");
}
@AfterEach
void afterEach() {
System.out.println(" NestedTest.afterEach()");
}
@AfterAll
void afterAll() {
System.out.println(" NestedTest.afterAll() *");
}
}
}
Execution result
JUnit5Test.beforeAll()
JUnit5Test.beforeEach()
JUnit5Test.test1()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
JUnit5Test.test2()
JUnit5Test.afterEach()
NestedTest.beforeAll() *
JUnit5Test.beforeEach()
NestedTest.beforeEach()
NestedTest.test1()
NestedTest.afterEach()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
NestedTest.beforeEach()
NestedTest.test2()
NestedTest.afterEach()
JUnit5Test.afterEach()
NestedTest.afterAll() *
JUnit5Test.afterAll()
--It is now possible to define the first and last processing only in @ Nested
.
--By default, a test instance is created for each test method
--This can be changed to instantiate each test class by default
--The method is one of the following
--Specify in system properties
--Specify in the JUnit Platform configuration file
--Specify as -Djunit.jupiter.testinstance.lifecycle.default = per_class
when specifying in system properties
--When specifying in the JUnit Platform configuration file, first place the junit-platform.properties
file in the class path root
--And specify junit.jupiter.testinstance.lifecycle.default = per_class
in this properties file
--Recommended for those who use JUnit Platform configuration files
--If you use system properties, you will have to remember to specify them in all execution environments.
--If you forget to set system properties and the behavior changes, it may be difficult to clarify the cause of the error.
package sample.junit5;
import org.junit.jupiter.api.RepeatedTest;
class JUnit5Test {
@RepeatedTest(3)
void test() {
System.out.println("test");
}
}
** Execution result (for ConsoleLauncher) **
Execution result(ConsoleLauncher)
test
test
test
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- test() [OK]
+-- repetition 1 of 3 [OK]
+-- repetition 2 of 3 [OK]
'-- repetition 3 of 3 [OK]
Test run finished after 98 ms
[ 3 containers found ]
[ 0 containers skipped ]
[ 3 containers started ]
[ 0 containers aborted ]
[ 3 containers successful ]
[ 0 containers failed ]
[ 3 tests found ]
[ 0 tests skipped ]
[ 3 tests started ]
[ 0 tests aborted ]
[ 3 tests successful ]
[ 0 tests failed ]
** Execution result (for Gradle) **
-When you annotate a method with @RepeatedTest, the test will be tested the number of times specified by value
. Will be repeated
--The display name of each repeated test will be repetition <current number of repetitions> of <total number of repetitions>
package sample.junit5;
import org.junit.jupiter.api.RepeatedTest;
class JUnit5Test {
@RepeatedTest(
name = "displayName={displayName}, currentRepetition={currentRepetition}, totalRepetitions={totalRepetitions}",
value = 3
)
void test() {
System.out.println("test");
}
}
** Execution result (for Console Launcher) **
Execution result(ConsoleLauncher)
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- test() [OK]
+-- displayName=test(), currentRepetition=1, totalRepetitions=3 [OK]
+-- displayName=test(), currentRepetition=2, totalRepetitions=3 [OK]
'-- displayName=test(), currentRepetition=3, totalRepetitions=3 [OK]
** Execution result (for Gradle) **
--You can specify the display name of the iterative test with the name
attribute.
--A dedicated placeholder is prepared for specifying the display name, and repeated information can be reflected in the display name.
--displayName
: Display name of test method
--Default is the name of the test method
--If @DisplayName
is specified, it will be used.
--currentRepetition
: Current number of repetitions (starting from 1)
--totalRepetitions
: Total number of repetitions
--A predefined display name pattern is provided in @RepetedTest
- LONG_DISPLAY_NAME
--The pattern is " {displayName} :: repetition {currentRepetition} of {totalRepetitions} "
package sample.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
class JUnit5Test {
@BeforeEach
void before(RepetitionInfo repetitionInfo) {
printRepetitionInfo("before", repetitionInfo);
}
@RepeatedTest(3)
void test(RepetitionInfo repetitionInfo) {
printRepetitionInfo(" test", repetitionInfo);
}
@AfterEach
void after(RepetitionInfo repetitionInfo) {
printRepetitionInfo("after", repetitionInfo);
}
private void printRepetitionInfo(String method, RepetitionInfo repetitionInfo) {
int currentRepetition = repetitionInfo.getCurrentRepetition();
int totalRepetitions = repetitionInfo.getTotalRepetitions();
System.out.printf("%s (%d/%d)%n", method, currentRepetition, totalRepetitions);
}
}
Execution result
before (1/3)
test (1/3)
after (1/3)
before (2/3)
test (2/3)
after (2/3)
before (3/3)
test (3/3)
after (3/3)
--The test method annotated with @RepeatedTest
holds the current iteration information [RepetitionInfo](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/RepetitionInfo" .html) Can accept objects as arguments
--You can also receive with @BeforeEach
, @AfterEach
--However, if the normal test methods declared with @Test
are mixed here, RepetitionInfo
cannot be resolved and a runtime error will occur.
--You can get the current number of repetitions with getCurrentRepetition ()
(1 start)
--You can get the total number of repetitions with getTotalRepetitions ()
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class JUnit5Test {
@ParameterizedTest
@ValueSource(strings = {"hoge", "fuga", "piyo"})
void test(String value) {
System.out.println("value=" + value);
}
}
** Execution result (for Console Launcher) **
Execution result(ConsoleLauncher)
value=hoge
value=fuga
value=piyo
Thanks for using JUnit! Support its development at https://junit.org/sponsoring
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- test(String) [OK]
+-- [1] hoge [OK]
+-- [2] fuga [OK]
'-- [3] piyo [OK]
Test run finished after 116 ms
[ 3 containers found ]
[ 0 containers skipped ]
[ 3 containers started ]
[ 0 containers aborted ]
[ 3 containers successful ]
[ 0 containers failed ]
[ 3 tests found ]
[ 0 tests skipped ]
[ 3 tests started ]
[ 0 tests aborted ]
[ 3 tests successful ]
[ 0 tests failed ]
** Execution result (for Gradle) **
-The method annotated by @ParameterizedTest receives the value used in the test as an argument. Will be executed while
--There are multiple ways to declare the value to be passed to the argument, but here [@ValueSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ ValueSource.html) using annotations
--@ValueSource
can statically declare parameters to pass to test methods with attributes such as strings
and ʻints --The source that generates the parameter (
@ValueSource` in the example of ↑) is called the parameter ** Source **.
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class JUnit5Test {
@ParameterizedTest
@EnumSource(TestEnum.class)
void test(TestEnum value) {
System.out.println("value=" + value);
}
enum TestEnum {
HOGE, FUGA, PIYO
}
}
Execution result
value=HOGE
value=FUGA
value=PIYO
-@EnumSource Using annotations, constants defined in ʻenumCan be used as a parameter --If you pass a
Class object of ʻenum
to value
, all the defined constants will be used as parameters.
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class JUnit5Test {
@ParameterizedTest
@EnumSource(value = TestEnum.class , names = {"HOGE", "PIYO"})
void test(TestEnum value) {
System.out.println("value=" + value);
}
enum TestEnum {
HOGE, FUGA, PIYO
}
}
Execution result
value=HOGE
value=PIYO
-You can narrow down the constants used in names
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class JUnit5Test {
@ParameterizedTest
@EnumSource(value = TestEnum.class, mode = EnumSource.Mode.EXCLUDE , names = {"HOGE", "PIYO"})
void test(TestEnum value) {
System.out.println("value=" + value);
}
enum TestEnum {
HOGE, FUGA, PIYO
}
}
Execution result
value=FUGA
-In mode, which condition is specified by names
You can specify whether to apply
-If EXCLUDE is specified, specify it with names
. Excludes constants
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class JUnit5Test {
@ParameterizedTest
@EnumSource(
value = TestEnum.class,
mode = EnumSource.Mode.MATCH_ALL,
names = {"^F.*", ".*H$"}
)
void matchAll(TestEnum value) {
System.out.println("matchAll() value=" + value);
}
@ParameterizedTest
@EnumSource(
value = TestEnum.class,
mode = EnumSource.Mode.MATCH_ANY,
names = {"^F.*", ".*H$"}
)
void matchAny(TestEnum value) {
System.out.println("matchAny() value=" + value);
}
enum TestEnum {
FIRST, SECOND, THIRD, FOURTH, FIFTH, SIXTH
}
}
Execution result
matchAll() value=FOURTH
matchAll() value=FIFTH
matchAny() value=FIRST
matchAny() value=FOURTH
matchAny() value=FIFTH
matchAny() value=SIXTH
-MATCH_ALL or [MATCH_ANY](https://junit. You can use org / junit5 / docs / current / api / org / junit / jupiter / params / provider / EnumSource.Mode.html # MATCH_ANY) to narrow down the target constants with regular expressions.
--MATCH_ALL
can be narrowed down to constants that match all the conditions
--MATCH_ANY
can be narrowed down to only constants that match any of the conditions
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
class JUnit5Test {
@ParameterizedTest
@MethodSource
void test(String value) {
System.out.println("value=" + value);
}
static List<String> test() {
return List.of("hoge", "fuga", "piyo");
}
}
Execution result
value=hoge
value=fuga
value=piyo
-@MethodSource can be used to source a method as a parameter
--By default, a static
method with the same name as the test method and no arguments is automatically selected as the source.
--The source method sets the value to be passed as a parameter to "Stream. Must be returned in "convertible type"
--What is a "type that can be converted to Stream
"is described in the Javadoc and user guide of MethodSource
as follows:
--Stream
, DoubleStream
, LongStream
, ʻIntStream,
Collection, ʻIterator
, ʻIterable`, Array of objects, Array of primitive types
――So, most ** like that ** feels good.
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
class JUnit5Test {
@ParameterizedTest
@MethodSource("sourceMethod")
void test(String value) {
System.out.println("value=" + value);
}
static List<String> sourceMethod() {
return List.of("HOGE", "FUGA", "PIYO");
}
static List<String> test() {
return List.of("hoge", "fuga", "piyo");
}
}
Execution result
value=HOGE
value=FUGA
value=PIYO
--The name of the source method can be specified by value
of @MethodSource
.
SourceClass
package sample.junit5;
import java.util.List;
class SourceClass {
static List<String> createSource() {
return List.of("foo", "bar");
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
class JUnit5Test {
@ParameterizedTest
@MethodSource("sample.junit5.SourceClass#createSource")
void test(String value) {
System.out.println("value=" + value);
}
}
Execution result
value=foo
value=bar
--In value
of @MethodSource
, you can use the method of the external class as the source by specifying the fully qualified name of the class #method name`.
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import static org.junit.jupiter.params.provider.Arguments.*;
class JUnit5Test {
@ParameterizedTest
@MethodSource
void test1(String string, int i, boolean bool) {
System.out.printf("test1() string=%s, i=%d, bool=%s%n", string, i, bool);
}
static List<Object[]> test1() {
return List.of(
new Object[]{"hoge", 11, false},
new Object[]{"fuga", 17, true},
new Object[]{"piyo", 19, true}
);
}
@ParameterizedTest
@MethodSource
void test2(String string, int i, boolean bool) {
System.out.printf("test2() string=%s, i=%d, bool=%s%n", string, i, bool);
}
static List<Arguments> test2() {
return List.of(
arguments("HOGE", 20, true),
arguments("FUGA", 23, false),
arguments("PIYO", 28, true)
);
}
}
Execution result
test1() string=hoge, i=11, bool=false
test1() string=fuga, i=17, bool=true
test1() string=piyo, i=19, bool=true
test2() string=HOGE, i=20, bool=true
test2() string=FUGA, i=23, bool=false
test2() string=PIYO, i=28, bool=true
--You can pass values to multiple formal parameters with one parameter.
--In that case, the source method must be implemented to return Stream
(what can be) of ʻObject []. --In this example,
List <Object []>` is returned.
--You can also use a dedicated container class called Arguments provided by jupiter. it can
-[arguments (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html#arguments (java.lang.Object ..) .)) or [of (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html#of(java.lang. There is a factory method called Object ...)) that you can use to create an instance.
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class JUnit5Test {
@ParameterizedTest
@CsvSource({"foo,bar,1", "'hoge,fuga','',2", "fizz,,3"})
void test(String s1, String s2, int i) {
System.out.printf("s1=[%s], s2=[%s], i=[%d]%n", s1, s2, i);
}
}
Execution result
s1=[foo], s2=[bar], i=[1]
s1=[hoge,fuga], s2=[], i=[2]
s1=[fizz], s2=[null], i=[3]
-Use @CsvSource to source static CSV-formatted text it can
--Use single quotes ('
) for quotes
--''
is treated as an empty string and complete spaces are treated as null
Folder tribute tax
`-src/test/
|-resources/
| `-test.csv
`-java/
`-sample/junit5/
`-JUnit5Test.java
test.csv
hoge,1
fuga,2
piyo,3
JUnit5Test
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
class JUnit5Test {
@ParameterizedTest
@CsvFileSource(resources = "/test.csv")
void test(String string, int i) {
System.out.printf("string=[%s], i=[%d]%n", string, i);
}
}
Execution result
string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]
-Use @CsvFileSource to specify the CSV file in the classpath as the source it can
--Specify the path of the CSV file to use with resources
--Encoding, line feed code, etc. can be specified by annotation attributes (see Javadoc).
MyArgumentsProvider
package sample.junit5;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import java.util.stream.Stream;
import static org.junit.jupiter.params.provider.Arguments.*;
public class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
return Stream.of(
arguments("hoge", 1),
arguments("fuga", 2),
arguments("piyo", 3)
);
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
class JUnit5Test {
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void test(String string, int i) {
System.out.printf("string=[%s], i=[%d]%n", string, i);
}
}
Execution result
string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]
-If you use @ArgumentsSource, you can use [ArgumentsProvider](https://junit. A class that implements org / junit5 / docs / current / api / org / junit / jupiter / params / provider / ArgumentsProvider.html) can be used as a source.
--Specify a Class
object of a class that implements ʻArgumentsProvider in
value`
--Can be used when reusing sources in multiple test classes
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import static org.junit.jupiter.params.provider.Arguments.*;
class JUnit5Test {
@ParameterizedTest
@MethodSource
void test(int i, long l, double d) {
System.out.printf("i=%s, l=%s, d=%s%n", i, l, d);
}
static List<Arguments> test() {
return List.of(arguments(10, 20, 30));
}
}
Execution result
i=10, l=20, d=30.0
--Parameters support ** widening primitive conversions **
--Mechanism for converting to larger size primitive types such as byte-> short
and ʻint-> long --Even if the actual parameter is ʻint
, the argument of the test method can be received by long
or double
.
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.List;
import static org.junit.jupiter.params.provider.Arguments.*;
class JUnit5Test {
@ParameterizedTest
@MethodSource
void test(
boolean bool, char c, double d, TestEnum e, File file,
Class<?> clazz, BigDecimal bd, Charset charset, LocalDateTime dateTime
) {
System.out.printf(
"bool=%s, c=%s, d=%s, e=%s, file=%s, clazz=%s, bd=%s, charset=%s, dateTime=%s%n",
bool, c, d, e, file, clazz, bd, charset, dateTime
);
}
static List<Arguments> test() {
return List.of(
arguments(
"true", "c", "12.34", "FOO", "path/to/file",
"java.lang.String", "98.76", "MS932", "2019-10-01T12:34:56"
)
);
}
enum TestEnum {
FOO, BAR
}
}
Execution result
bool=true, c=c, d=12.34, e=FOO, file=path\to\file, clazz=class java.lang.String, bd=98.76, charset=windows-31j, dateTime=2019-10-01T12:34:56
--Even if the parameter is a character string, it implicitly converts the type of the formal argument of the test method. --It supports various types, and most of the frequently used types that exist in the standard API are supported.
[^ 3]: Error if length ()
of the original String
is not 1
[^ 4]: null
is an error
package sample.junit5;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Arrays;
import java.util.List;
class JUnit5Test {
@ParameterizedTest
@MethodSource
void test(Class<?> clazz) {
System.out.println(clazz);
}
static List<String> test() {
return Arrays.asList(
"int",
"int[]",
"int[][]",
"[I",
"[[I",
"java.lang.String",
"java.lang.String[]",
"java.lang.String[][]",
"[Ljava.lang.String;",
"[[Ljava.lang.String;",
"sample.junit5.JUnit5Test",
"sample.junit5.JUnit5Test$InnerClass"
);
}
class InnerClass {}
}
Execution result
int
class [I
class [[I
class [I
class [[I
class java.lang.String
class [Ljava.lang.String;
class [[Ljava.lang.String;
class [Ljava.lang.String;
class [[Ljava.lang.String;
class sample.junit5.JUnit5Test
class sample.junit5.JUnit5Test$InnerClass
--Primitive types can specify the type name as it is (ʻint,
long,
float, etc ...) --An array of primitive types can be specified with a notation such as ʻint []
, ʻint [] [] --The array can be obtained with [Class.getName ()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Class.html#getName ()) Can be specified in the same format as the string (
[[I,
[Ljava.lang.String; `, etc ...)
--Other reference types are specified in binary name (Internally [ClassLoader.loadClass (String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ClassLoader.html#loadClass (java) .lang.String))) is used)
package sample.junit5;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.TestFactory;
import java.util.List;
import static org.junit.jupiter.api.DynamicTest.*;
class JUnit5Test {
@TestFactory
List<DynamicNode> testFactory() {
return List.of(
dynamicTest("Hoge", () -> System.out.println("Dynamic Hoge!!")),
dynamicTest("Fuga", () -> System.out.println("Dynamic Fuga!!"))
);
}
}
Execution result
Dynamic Hoge!!
Dynamic Fuga!!
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- testFactory() [OK]
+-- Hoge [OK]
'-- Fuga [OK]
-@TestFactory You can dynamically generate test cases by using annotations.
--Methods annotated with @TestFactory
now return a collection of DynamicNode Implement
――Strictly speaking, a "collection" can be one of the following:
- java.util.Collection
- java.lang.Iterable
- java.util.Iterator
- java.util.stream.Stream
--Array
--If you return with Stream
, Jupiter will doclose ()
, so it is safe to use Stream
generated by Files.lines ()
.
--DynamicNode
itself is an abstract class, so it's actually a subclass DynamicTest or [DynamicContainer]( Use one of https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicContainer.html)
--In the above example, [dynamicTest (String, Executable)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicTest.html#dynamicTest (java.lang.String, org) I'm creating an instance of DynamicTest
using a factory method called .junit.jupiter.api.function.Executable))
--The first argument is the name of the test
--The second argument is the content of the test
package sample.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import java.util.List;
import static org.junit.jupiter.api.DynamicTest.*;
class JUnit5Test {
@BeforeEach
void beforeEach() {
System.out.println("beforeEach()");
}
@TestFactory
List<DynamicNode> testFactory() {
System.out.println(" testFactory()");
return List.of(
dynamicTest("Hoge", () -> System.out.println(" Dynamic Hoge!!")),
dynamicTest("Fuga", () -> System.out.println(" Dynamic Fuga!!"))
);
}
@Test
void test() {
System.out.println(" test()");
}
@AfterEach
void afterEach() {
System.out.println("afterEach()");
}
}
Execution result
beforeEach()
testFactory()
Dynamic Hoge!!
Dynamic Fuga!!
afterEach()
beforeEach()
test()
afterEach()
--@BeforeEach
and @AfterEach
are only executed before and after the method in which @TestFactory
is set, not before and after each dynamic test.
package sample.junit5;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.TestFactory;
import java.util.List;
import static org.junit.jupiter.api.DynamicContainer.*;
import static org.junit.jupiter.api.DynamicTest.*;
class JUnit5Test {
@TestFactory
List<DynamicNode> testFactory() {
return List.of(
dynamicContainer("Dynamic Container 1", List.of(
dynamicTest("Foo", () -> System.out.println("Dynamic Foo.")),
dynamicContainer("Dynamic Container 1-1", List.of(
dynamicTest("Hoge", () -> System.out.println("Dynamic Hoge.")),
dynamicTest("Fuga", () -> System.out.println("Dynamic Fuga."))
))
)),
dynamicContainer("Dynamic Container 2", List.of(
dynamicTest("Fizz", () -> System.out.println("Dynamic Fizz.")),
dynamicTest("Buzz", () -> System.out.println("Dynamic Buzz."))
))
);
}
}
Execution result
Dynamic Foo.
Dynamic Hoge.
Dynamic Fuga.
Dynamic Fizz.
Dynamic Buzz.
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- testFactory() [OK]
+-- Dynamic Container 1 [OK]
| +-- Foo [OK]
| '-- Dynamic Container 1-1 [OK]
| +-- Hoge [OK]
| '-- Fuga [OK]
'-- Dynamic Container 2 [OK]
+-- Fizz [OK]
'-- Buzz [OK]
-DynamicContainer allows you to nest dynamic tests
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
--junit-platform.properties
is placed in the class path root
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@Execution(ExecutionMode.CONCURRENT) //★ Don't forget to put this on!
class JUnit5Test {
@BeforeAll
static void beforeAll() {
printThread("beforeAll()");
}
@BeforeEach
void beforeEach(TestInfo testInfo) {
String name = testInfo.getDisplayName();
printThread(" " + name + ":beforeEach()");
}
@Test
void test1() {
printThread(" test1()");
}
@Test
void test2() {
printThread(" test2()");
}
@AfterEach
void afterEach(TestInfo testInfo) {
String name = testInfo.getDisplayName();
printThread(" " + name + ":afterEach()");
}
@AfterAll
static void afterAll() {
printThread("afterAll()");
}
private static void printThread(String test) {
String name = Thread.currentThread().getName();
System.out.printf("%s@%s%n", test, name);
}
}
Execution result
beforeAll()@ForkJoinPool-1-worker-3
test1():beforeEach()@ForkJoinPool-1-worker-5
test2():beforeEach()@ForkJoinPool-1-worker-7
test2()@ForkJoinPool-1-worker-7
test1()@ForkJoinPool-1-worker-5
test1():afterEach()@ForkJoinPool-1-worker-5
test2():afterEach()@ForkJoinPool-1-worker-7
afterAll()@ForkJoinPool-1-worker-3
--By default, all test methods are executed sequentially in a single thread
--If you set junit.jupiter.execution.parallel.enabled = true
in the configuration file (junit-platform.properties
placed in the class path root), parallel execution will be enabled.
--However, this setting alone does not change the test method and it is executed sequentially in a single thread.
--In order to execute test methods in parallel, @Execution annotation Must be set to value
to specify CONCURRENT
-If SAME_THREAD is specified, it will be executed in the same thread as the parent.
--The default is this setting
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
package sample.junit5;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
void test1() {
printThread("test1()");
}
@Test
void test2() {
printThread("test2()");
}
private static void printThread(String test) {
String name = Thread.currentThread().getName();
System.out.printf("%s@%s%n", test, name);
}
}
Execution result
test1()@ForkJoinPool-1-worker-5
test2()@ForkJoinPool-1-worker-7
--If you specify junit.jupiter.execution.parallel.mode.default = concurrent
in the configuration file, the default execution mode will be parallel execution (with ʻExecutionMode.CONCURRENT` specified).
--If you specify junit.jupiter.execution.parallel.mode.default = concurrent
, most test methods will be executed in parallel.
--However, as an exception, test classes and methods with the following settings are not executed in parallel even if the default execution mode is changed.
--Test class with Lifecycle.PER_CLASS
--Test method with MethodOrderer
other than MethodOrderer.Random
Example of not executing in parallel even if the default execution mode is changed
package sample.junit5;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
class JUnit5Test {
@Nested
class StandardNestedTest {
@Test
void test1() {
printThread("StandardNestedTest.test1()");
}
@Test
void test2() {
printThread("StandardNestedTest.test2()");
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PerClassTest {
@Test
void test1() {
printThread("PerClassTest.test1()");
}
@Test
void test2() {
printThread("PerClassTest.test2()");
}
}
@Nested
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
class OrderedTest {
@Test
void test1() {
printThread("OrderedTest.test1()");
}
@Test
void test2() {
printThread("OrderedTest.test2()");
}
}
private static void printThread(String test) {
String name = Thread.currentThread().getName();
System.out.printf("%s@%s%n", test, name);
}
}
Execution result
StandardNestedTest.test1()@ForkJoinPool-1-worker-15
StandardNestedTest.test2()@ForkJoinPool-1-worker-13
PerClassTest.test1()@ForkJoinPool-1-worker-7
OrderedTest.test1()@ForkJoinPool-1-worker-5
PerClassTest.test2()@ForkJoinPool-1-worker-7
OrderedTest.test2()@ForkJoinPool-1-worker-5
--The test method of StandardNestedTest
is running in a different thread
--But the test methods for PerClassTest
and ʻOrderedTestare each running in the same thread. --If you want to execute the test methods of these classes in parallel [^ 5], make sure that the test class is thread-safe, and then explicitly specify
@Execution (ExecutionMode.CONCURRENT)`.
[^ 5]: It's a strange story to run tests in parallel with the order specified by @TestMethodOrder
.
Explicitly specify parallel execution
package sample.junit5;
...
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
class JUnit5Test {
@Nested
class StandardNestedTest {
...
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.CONCURRENT)
class PerClassTest {
...
}
@Nested
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
@Execution(ExecutionMode.CONCURRENT)
class OrderedTest {
...
}
private static void printThread(String test) {...}
}
Execution result
PerClassTest.test1()@ForkJoinPool-1-worker-15
StandardNestedTest.test1()@ForkJoinPool-1-worker-13
PerClassTest.test2()@ForkJoinPool-1-worker-7
OrderedTest.test1()@ForkJoinPool-1-worker-11
OrderedTest.test2()@ForkJoinPool-1-worker-5
StandardNestedTest.test2()@ForkJoinPool-1-worker-9
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.classes.default=concurrent
FooTest.java
package sample.junit5;
import org.junit.jupiter.api.Test;
class FooTest {
@Test
void test1() {
printThread("FooTest.test1()");
}
@Test
void test2() {
printThread("FooTest.test2()");
}
private static void printThread(String test) {
String name = Thread.currentThread().getName();
System.out.printf("%s@%s%n", test, name);
}
}
BarTest.java
package sample.junit5;
import org.junit.jupiter.api.Test;
class BarTest {
@Test
void test1() {
printThread("BarTest.test1()");
}
@Test
void test2() {
printThread("BarTest.test2()");
}
private static void printThread(String test) {
String name = Thread.currentThread().getName();
System.out.printf("%s@%s%n", test, name);
}
}
Execution result
BarTest.test1()@ForkJoinPool-1-worker-3
FooTest.test1()@ForkJoinPool-1-worker-7
BarTest.test2()@ForkJoinPool-1-worker-3
FooTest.test2()@ForkJoinPool-1-worker-7
--If you specify junit.jupiter.execution.parallel.mode.classes.default = concurrent
in the config file, you can only change the default execution mode of top-level classes.
--The junit.jupiter.execution.parallel.mode.default = concurrent
that you set earlier controls method-level parallel execution, and this time it is not set (hence the default same_thread
. Same state as specified)
--As a result, each test class is executed in a separate thread, and the methods in the test class are executed sequentially in a single thread.
--FooTest
and BarTest
are running in different threads (ForkJoinPool-1-worker-7
and ForkJoinPool-1-worker-3
)
--But each method in FooTest
is executed in one thread (ForkJoinPool-1-worker-7
)
--There are a total of 4 combinations of settings for junit.jupiter.execution.parallel.mode.default
and junit.jupiter.execution.parallel.mode.classes.default
, and each works as follows.
--If junit.jupiter.execution.parallel.mode.classes.default
(default behavior for each class) is not specified, the same setting value as junit.jupiter.execution.parallel.mode.default
become
junit-platform.properties
jjunit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.dynamic.factor=2
ParallelismCounter
package sample.junit5;
import java.util.concurrent.atomic.AtomicInteger;
public class ParallelismCounter {
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicInteger max = new AtomicInteger(0);
public void increment() {
this.max.set(Math.max(this.max.get(), this.counter.incrementAndGet()));
}
public void decrement() {
this.counter.decrementAndGet();
}
public int getMaxCount() {
return this.max.get();
}
}
--Class for counting the number of threads running concurrently
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.util.stream.IntStream;
import java.util.stream.Stream;
class JUnit5Test {
private long begin;
private ParallelismCounter counter = new ParallelismCounter();
@BeforeEach
void beforeEach() {
begin = System.currentTimeMillis();
}
@TestFactory
Stream<DynamicNode> testFactory() {
return IntStream
.range(0, 20)
.mapToObj(i -> DynamicTest.dynamicTest("test" + i, () -> {
counter.increment();
Thread.sleep(1000);
counter.decrement();
}));
}
@AfterEach
void printThreadNames() {
System.out.println(System.currentTimeMillis() - begin + "ms");
System.out.println("Available Processors = " + Runtime.getRuntime().availableProcessors());
System.out.println("Max Parallelism Count = " + counter.getMaxCount());
System.out.println("Active Thread Count = " + Thread.activeCount());
Thread[] activeThreads = new Thread[Thread.activeCount()];
Thread.enumerate(activeThreads);
IntStream.range(0, activeThreads.length)
.mapToObj(i -> "[" + i + "] " + activeThreads[i].getName())
.forEach(System.out::println);
}
}
--Using the dynamic test mechanism, we are creating 20 tests that take 1 second. --Various information is output after the test is completed.
Execution result
2033ms
Available Processors = 8
Max Parallelism Count = 16
Active Thread Count = 18
[0] main
[1] ForkJoinPool-1-worker-19
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-23
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-27
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-31
[8] ForkJoinPool-1-worker-17
[9] ForkJoinPool-1-worker-3
[10] ForkJoinPool-1-worker-21
[11] ForkJoinPool-1-worker-7
[12] ForkJoinPool-1-worker-25
[13] ForkJoinPool-1-worker-11
[14] ForkJoinPool-1-worker-29
[15] ForkJoinPool-1-worker-15
[16] ForkJoinPool-1-worker-1
[17] ForkJoinPool-1-worker-33
--You can adjust the number of threads running in parallel by specifying junit.jupiter.execution.parallel.config.dynamic.factor
in the config file.
--The number of threads executed in parallel is the number obtained by multiplying the value specified in factor
by the number of processors (cores) in the execution environment.
--In the verified environment, the number of cores was 8 and 2 was specified for factor
, so the number of threads executed in parallel is 16.
--In addition, ForkJoinPool is used to execute parallel execution. And the number of threads pooled can be greater than the number of threads running in parallel (the number of active threads is greater than the number of threads running in parallel)
--If factor
is not specified, the default is 1.
--By the way, factor
is converted to BigDecimal
behind the scenes, so you can specify a small number.
--Truncate after the decimal point (finally intValue () Converted to ʻint`)
junit-paltform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.dynamic.factor=0.5
Execution result
5042ms
Available Processors = 8
Max Parallelism Count = 4
Active Thread Count = 6
[0] main
[1] ForkJoinPool-1-worker-3
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-7
[4] ForkJoinPool-1-worker-1
[5] ForkJoinPool-1-worker-9
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=6
dynamic
is specified.Execution result
4031ms
Available Processors = 8
Max Parallelism Count = 6
Active Thread Count = 8
[0] main
[1] ForkJoinPool-1-worker-3
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-7
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-11
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-15
--If you specify fixed
in junit.jupiter.execution.parallel.config.strategy
, you can specify a fixed number of parallels.
--The number of parallels is specified by junit.jupiter.execution.parallel.config.fixed.parallelism
build.gradle
...
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter:5.5.2"
testImplementation "org.junit.jupiter:junit-jupiter-engine:5.5.2" // ★
}
-- junit-jupiter-engine
can be referenced only at runtime by default, so it is specified in testImplementation
so that it can also be referenced at compile time.
MyParallelExecutionConfigurationStrategy
package sample.junit5;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfiguration;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy;
public class MyParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy {
@Override
public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) {
return new ParallelExecutionConfiguration() {
@Override
public int getParallelism() {
return 7;
}
@Override
public int getMinimumRunnable() {
return 7;
}
@Override
public int getMaxPoolSize() {
return 7;
}
@Override
public int getCorePoolSize() {
return 7;
}
@Override
public int getKeepAliveSeconds() {
return 30;
}
};
}
}
--Create a class that implements ParallelExecutionConfigurationStrategy
--The createConfiguration ()
method returns an instance that implements ParallelExecutionConfiguration
.
--The Getter defined in ParallelExecutionConfiguration
is used when generating ForkJoinPool
.
--The meaning of each value is [Javadoc of constructor] of ForkJoinPool
(https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html" #% 3Cinit% 3E (int, java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory, java.lang.Thread.UncaughtExceptionHandler, boolean, int, int, int, java.util.function.Predicate, long, java.util.concurrent. See TimeUnit))
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=custom
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.custom.class=sample.junit5.MyParallelExecutionConfigurationStrategy
--Specify custom
in junit.jupiter.execution.parallel.config.strategy
--Specify the implementation class of your own ParallelExecutionConfigurationStrategy
with junit.jupiter.execution.parallel.config.custom.class
dynamic
.Execution result
4034ms
Available Processors = 8
Max Parallelism Count = 7
Active Thread Count = 8
[0] main
[1] ForkJoinPool-1-worker-3
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-7
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-11
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-15
--You can see that they are executed in parallel according to the setting of ParallelExecutionConfiguration
returned by MyParallelExecutionConfigurationStrategy
.
When there is no exclusive control
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@Execution(ExecutionMode.CONCURRENT)
class JUnit5Test {
static int n = 0;
@Test
void test1() throws Exception {
process("test1");
}
@Test
void test2() throws Exception {
process("test2");
}
@Test
void test3() throws Exception {
process("test3");
}
private void process(String name) {
System.out.println("begin " + name);
for (int i=0; i<10000; i++) {
n++;
}
System.out.println("end " + name);
}
@AfterAll
static void afterAll() {
System.out.println("n = " + n);
}
}
--The class is executed in parallel and the static
variable n
is incremented 10,000 times from each of the three test methods.
Execution result
begin test3
begin test1
begin test2
end test1
end test2
end test3
n = 13394
--Of course, the result will not be 30,000 because it is out of sync.
When exclusive control is put
package sample.junit5;
...
import org.junit.jupiter.api.parallel.ResourceLock;
@Execution(ExecutionMode.CONCURRENT)
class JUnit5Test {
static int n = 0;
@Test
@ResourceLock("hoge")
void test1() throws Exception { ... }
@Test
@ResourceLock("hoge")
void test2() throws Exception { ... }
@Test
@ResourceLock("hoge")
void test3() throws Exception { ... }
private void process(String name) { ... }
@AfterAll
static void afterAll() { ... }
}
--Set @ResourceLock
annotation to each method
Execution result
begin test1
end test1
begin test2
end test2
begin test3
end test3
n = 30000
--The execution of each method was synchronized and the value of n
became 30,000.
-If you set @ResourceLock to a class or method, the test cases in it will be Will be synchronized
--Specify the key string for controlling exclusion in value
--Synchronized between @ResourceLock
with the same key string
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
import org.junit.jupiter.api.parallel.ResourceLock;
@Execution(ExecutionMode.CONCURRENT)
class JUnit5Test {
@Test
@ResourceLock(value = "hoge", mode = ResourceAccessMode.READ_WRITE)
void test1() throws Exception {
process("test1(READ_WRITE)");
}
@Test
@ResourceLock(value = "hoge", mode = ResourceAccessMode.READ)
void test2() throws Exception {
process("test2(READ)");
}
@Test
@ResourceLock(value = "hoge", mode = ResourceAccessMode.READ)
void test3() throws Exception {
process("test3(READ)");
}
private void process(String name) throws Exception {
System.out.println("begin " + name);
Thread.sleep(500);
System.out.println("end " + name);
}
}
Execution result
begin test1(READ_WRITE)
end test1(READ_WRITE)
begin test2(READ)
begin test3(READ)
end test2(READ)
end test3(READ)
--You can specify the access mode with mode
of @ResourceLock
--Access mode is specified by the constant defined in ResourceAccessMode
--The combination of exclusive control by each access mode looks like the following.
--It is possible to execute in parallel between READ
s, but exclusive control is performed when READ_WRITE
is involved.
--READ
is specified for the test method that only reads the data and does not update, and READ_WRITE
is specified for the test method that updates the data.
DefaultMethodTest
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.List;
import static org.junit.jupiter.api.DynamicTest.*;
interface DefaultMethodTest {
@BeforeAll
static void beforeAll() {
System.out.println("beforeAll()");
}
@BeforeEach
default void beforeEach() {
System.out.println(" beforeEach()");
}
@Test
default void test() {
System.out.println(" test()");
}
@RepeatedTest(3)
default void repeatedTest() {
System.out.println(" repeatedTest()");
}
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
default void parameterizedTest(String param) {
System.out.println(" parameterizedTest(" + param + ")");
}
@TestFactory
default List<DynamicNode> testFactory() {
return List.of(
dynamicTest("DynamicTest1", () -> System.out.println(" testFactory(1)")),
dynamicTest("DynamicTest2", () -> System.out.println(" testFactory(2)"))
);
}
@AfterEach
default void afterEach() {
System.out.println(" afterEach()");
}
@AfterAll
static void afterAll() {
System.out.println("afterAll()");
}
}
JUnit5Test
package sample.junit5;
class JUnit5Test implements DefaultMethodTest {}
Execution result
beforeAll()
beforeEach()
repeatedTest()
afterEach()
beforeEach()
repeatedTest()
afterEach()
beforeEach()
repeatedTest()
afterEach()
beforeEach()
testFactory(1)
testFactory(2)
afterEach()
beforeEach()
test()
afterEach()
beforeEach()
parameterizedTest(one)
afterEach()
beforeEach()
parameterizedTest(two)
afterEach()
beforeEach()
parameterizedTest(three)
afterEach()
afterAll()
--You can also define tests with the default methods of the interface --If you ʻimplements` the interface in the target class, the test will be executed.
JUnit Jupiter has a mechanism called ** extension model **, which makes it easy to introduce any extension.
Hello World
MyExtension
package sample.junit5;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println("MyExtension.beforeEach()");
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println("MyExtension.afterEach()");
}
}
--To create an extension, first create a class that implements the Extension interface To do
--However, ʻExtension itself is a marker interface and no method is defined. --Actually, [BeforeEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html) and [AfterEachCallback](afterEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html) which inherited ʻExtension
Implement subinterfaces defined for each extension point, such as https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterEachCallback.html)
--For BeforeEachCallback
, thebeforeEach ()
method that is called back before each test is defined.
ʻAfterEachCallback defines a ʻafterEach ()
method that will be called back after each test.
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MyExtension.class)
class JUnit5Test {
@Test
void test1() {
System.out.println(" test1()");
}
@Test
void test2() {
System.out.println(" test2()");
}
}
--As one way to actually use a class that implements an extension in a test, [@ExtendWith](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension /ExtendWith.html) There is a way to use annotations
--Set the @ExtendWith
annotation where you want to apply the extension, and specify the Class
object of the extension you want to apply to value
.
Execution result
MyExtension.beforeEach()
test1()
MyExtension.afterEach()
MyExtension.beforeEach()
test2()
MyExtension.afterEach()
--With this alone, the processing defined by the extension function will be executed before and after each test.
--In the example, @ExtendWith
was specified for the class, but the extension can be partially applied by specifying it in the method.
test1()If you apply the extension only to the method
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
class JUnit5Test {
@Test
@ExtendWith(MyExtension.class)
void test1() {
System.out.println(" test1()");
}
@Test
void test2() {
System.out.println("test2()");
}
}
Execution result
MyExtension.beforeEach()
test1()
MyExtension.afterEach()
test2()
ʻExtension` Interfaces that are extension points that inherit from the interface are as follows.
interface | Description |
---|---|
ExecutionCondition | Controls whether the test is run. |
TestInstanceFactory | Create a test instance. |
TestInstancePostProcessor | Perform initialization processing after test instance generation. |
ParameterResolver | Resolve arguments such as test methods and lifecycle methods. |
BeforeAllCallback BeforeEachCallback BeforeTestExecutionCallback AfterTestExecutionCallback AfterEachCallback AfterAllCallback |
Execute processing along the life cycle, such as before and after executing the test. |
TestWatcher | Execute post-processing according to the execution result of the test method. |
TestExecutionExceptionHandler | Handle exceptions thrown during test execution. |
LifecycleMethodExecutionExceptionHandler | @BeforeEach Handle exceptions thrown by lifecycle methods such as. |
TestTemplateInvocationContextProvider | Performs preparatory processing to execute the same test in different contexts. |
Utility classes (support classes) that can be used universally when implementing extension classes are provided.
Class
Exception handling and troublesome description are omitted, and the operation can be performed in a concise manner.
For the time being, keep in mind that "this kind of thing exists", and when you start creating an extension class, think "Oh, can you use this support class?" And look for the method you want. I think it's good.
These classes are not internal utilities, they are provided as an aid when a third party creates their own TestEngine
or extension, so you can use them with confidence.
MyExecutionCondition
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyExecutionCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (context.getTestMethod().isPresent()) {
System.out.println("# Test method = " + context.getRequiredTestMethod().getName());
return context.getDisplayName().contains("o")
? ConditionEvaluationResult.enabled("Test name has 'o'.")
: ConditionEvaluationResult.disabled("Test name does not have 'o'.");
} else {
System.out.println("# Test class = " + context.getRequiredTestClass().getSimpleName());
return ConditionEvaluationResult.enabled("This is test class.");
}
}
}
--Only valid test methods that include " o "
in their display name
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExecutionCondition;
import java.util.List;
@ExtendWith(MyExecutionCondition.class)
class JUnit5Test {
@BeforeEach
void beforeEach() {
System.out.println("beforeEach()");
}
@Test
void hoge() {
System.out.println(" hoge()");
}
@Test
void fuga() {
System.out.println(" fuga()");
}
@Nested
class NestedClass {
@Test
void piyo() {
System.out.println(" piyo()");
}
}
@TestFactory
List<DynamicNode> testFactory() {
return List.of(
DynamicTest.dynamicTest("DynamicTest1", () -> System.out.println(" dynamicTest1")),
DynamicTest.dynamicTest("DynamicTest2", () -> System.out.println(" dynamicTest2"))
);
}
}
Execution result
# Test class = JUnit5Test
# Test method = testFactory
beforeEach()
dynamicTest1
dynamicTest2
# Test method = fuga
# Test method = hoge
beforeEach()
hoge()
# Test class = NestedClass
# Test method = piyo
beforeEach()
piyo()
--ExecutionCondition allows you to control whether the test is run
--Since the ʻevaluateExecutionCondition (ExtensionContext) method is called for each container [^ 6] and method, the return value controls whether to execute the target test. --[ConditionEvaluationResult.enabled (String)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ConditionEvaluationResult.html#enabled (java.lang.) Returns the value generated by String)) --If not executed, [ConditionEvaluationResult.disabled (String)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ConditionEvaluationResult.html#disabled (java.lang. Returns the value generated by String)) --In the argument, describe the reason why the target was enabled / disabled. --ʻEvaluateExecutionCondition (ExtensionContext)
If you use ExtensionContext that the method receives as an argument, You can see information about the target test method or container
-Some methods such as getTestMethod () depend on the condition The return type is ʻOptionalbecause the value may not exist in --If you know that it will never be
null, then [getRequiredTestMethod ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext. You can also use a method with
Required` like html # getRequiredTestMethod ())
[^ 6]: The documentation says container
, but it doesn't say exactly what container
refers to (probably something like DynamicContainer
for test classes or dynamic tests). I think it refers to the catamari that summarizes the test methods)
MyTestInstanceFactory
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstanceFactory;
import org.junit.jupiter.api.extension.TestInstanceFactoryContext;
import org.junit.jupiter.api.extension.TestInstantiationException;
import org.junit.platform.commons.support.ReflectionSupport;
public class MyTestInstanceFactory implements TestInstanceFactory {
@Override
public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) throws TestInstantiationException {
Class<?> testClass = factoryContext.getTestClass();
System.out.println("===========================================");
System.out.println("* testClass=" + testClass);
System.out.println("* outerInstance=" + factoryContext.getOuterInstance().orElse("<empty>"));
return factoryContext
.getOuterInstance()
.map(outerInstance -> {
Object instance = ReflectionSupport.newInstance(testClass, outerInstance);
System.out.println("* outerInstance [" + outerInstance.hashCode() + "]");
System.out.println("* testInstance [" + instance.hashCode() + "]");
return instance;
})
.orElseGet(() -> {
Object instance = ReflectionSupport.newInstance(testClass);
System.out.println("* testInstance [" + instance.hashCode() + "]");
return instance;
});
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestInstanceFactory;
import java.util.List;
@ExtendWith(MyTestInstanceFactory.class)
class JUnit5Test {
@Test
void test() {
System.out.println("JUnit5Test.test() [" + this.hashCode() + "]");
}
@Nested
class NestedClass {
@Test
void test() {
System.out.println("NestedClass.test() [" + this.hashCode() + "]");
}
}
@TestFactory
List<DynamicNode> testFactory() {
return List.of(
DynamicTest.dynamicTest("DynamicTest1", () -> System.out.println("JUnit5Test.dynamicTest1() [" + this.hashCode() + "]")),
DynamicTest.dynamicTest("DynamicTest2", () -> System.out.println("JUnit5Test.dynamicTest2() [" + this.hashCode() + "]"))
);
}
}
Execution result
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [1781155104]
JUnit5Test.dynamicTest1() [1781155104]
JUnit5Test.dynamicTest2() [1781155104]
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [667449440]
JUnit5Test.test() [667449440]
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [1846668301]
===========================================
* testClass=class sample.junit5.JUnit5Test$NestedClass
* outerInstance=sample.junit5.JUnit5Test@6e11ec0d
* outerInstance [1846668301]
* testInstance [1282836833]
NestedClass.test() [1282836833]
-TestInstanceFactory can be used to manualize the generation of test instances.
--The createTestInstance ()
method is called before each test method is executed
-* If the life cycle is set to PER_CLASS
, it will be called only once for each class.
--The instance returned by createTestInstance ()
will be used in the test
--If there is an internal test class defined by @ Nested
, it will be called once to generate the outer class, and then the method will be called again to generate the internal test class.
--In the case of an inner class, getOuterInstance ()
of TestInstanceFactoryContext
is not empty, so you can judge by that.
MyTestInstancePostProcessor
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
public class MyTestInstancePostProcessor implements TestInstancePostProcessor {
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
System.out.println("testInstance.hash = " + testInstance.hashCode());
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestInstancePostProcessor;
@ExtendWith(MyTestInstancePostProcessor.class)
class JUnit5Test {
@Test
void test1() {
System.out.println("test1() [" + this.hashCode() + "]");
}
@Test
void test2() {
System.out.println("test2() [" + this.hashCode() + "]");
}
}
Execution result
testInstance.hash = 756008141
test1() [756008141]
testInstance.hash = 282044315
test2() [282044315]
-Use TestInstancePostProcessor to get a test instance before the test method is executed. Can be received and perform any processing
--It seems to be used to inject some dependency into the test instance or call an initialization method.
――Isn't it okay with BeforeEachCallback
?
--If it is BeforeEachCallback
, the test instance will be obtained from ʻExtensionContext, so I wonder if it is ʻOptional
orgetRequiredInstance ()
.
――After that, there may be a merit that you can clarify the implementation intention of "performing post-processing for the test instance" (speculation).
MyParameterResolver
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import java.lang.reflect.Executable;
import java.lang.reflect.Parameter;
import java.util.Optional;
public class MyParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
Executable executable = parameterContext.getDeclaringExecutable();
int index = parameterContext.getIndex();
Parameter parameter = parameterContext.getParameter();
Optional<Object> target = parameterContext.getTarget();
System.out.printf(
"target=%s, executable=%s, index=%d, parameter=%s%n",
target.orElse("<empty>"),
executable.getName(),
index,
parameter.getName()
);
return true;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
Class<?> type = parameterContext.getParameter().getType();
if (type.equals(String.class)) {
return "Hello";
} else if (type.equals(int.class)) {
return 999;
} else {
return 12.34;
}
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyParameterResolver;
import java.util.List;
@ExtendWith(MyParameterResolver.class)
class JUnit5Test {
@BeforeEach
void beforeEach(int i) {
System.out.printf("beforeEach(i=%d)%n", i);
}
@TestFactory
List<DynamicNode> dynamicTest(String string) {
return List.of(DynamicTest.dynamicTest("DynamicTest", () -> System.out.printf("dynamicTest(string=%s)%n", string)));
}
@Test
void test1(String string, double d, int i) {
System.out.printf("test1(string=%s, d=%f, i=%d)%n", string, d, i);
}
@Nested
class NestedClass {
@Test
void test2(double d) {
System.out.printf("test2(d=%f)%n", d);
}
}
@AfterEach
void afterEach() {
System.out.println("-----------------------------------------");
}
}
Execution result
target=sample.junit5.JUnit5Test@5e101011, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test@5e101011, executable=dynamicTest, index=0, parameter=string
dynamicTest(string=Hello)
-----------------------------------------
target=sample.junit5.JUnit5Test@4d3b0c46, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=0, parameter=string
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=1, parameter=d
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=2, parameter=i
test1(string=Hello, d=12.340000, i=999)
-----------------------------------------
target=sample.junit5.JUnit5Test@7d9ea927, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test$NestedClass@710edef, executable=test2, index=0, parameter=d
test2(d=12.340000)
-----------------------------------------
-ParameterResolver can be used to arbitrarily resolve the arguments of various methods. Become
--Not only @Test
but also method arguments such as @TestFactory
and @BeforeEach
can be resolved.
--supportsParameter ()
is called for each method argument
-From ParameterContext, you can refer to the meta information of the argument.
--Implemented to return true
if it supports argument resolution, otherwise it returns false
--If supportsParameter ()
returns true
, thenresolveParameter ()
is called.
--Implement this method to return the value of the resolved argument
--Note that in order to get the argument name with Parameter.getName ()
, you need to add the -parameters
option when compiling with javac
.
--With no options, names like ʻarg0, ʻarg1
--If you are building with Gradle, you can set it like compileTestJava.options.complierArgs + ="-parameters "
MyLifeCycleCallback
package sample.junit5.extension;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyLifeCycleCallback
implements BeforeAllCallback,
BeforeEachCallback,
BeforeTestExecutionCallback,
AfterTestExecutionCallback,
AfterEachCallback,
AfterAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
System.out.println("BeforeAllCallback");
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println(" BeforeEachCallback");
}
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception {
System.out.println(" BeforeTestExecutionCallback");
}
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
System.out.println(" AfterTestExecutionCallback");
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println(" AfterEachCallback");
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
System.out.println("AfterAllCallback");
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyLifeCycleCallback;
@ExtendWith(MyLifeCycleCallback.class)
class JUnit5Test {
@BeforeAll
static void beforeAll() {
System.out.println(" beforeAll()");
}
@BeforeEach
void beforeEach() {
System.out.println(" beforeEach()");
}
@Test
void test() {
System.out.println(" test()");
}
@AfterEach
void afterEach() {
System.out.println(" afterEach()");
}
@AfterAll
static void afterAll() {
System.out.println(" afterAll()");
}
}
Execution result
BeforeAllCallback
beforeAll()
BeforeEachCallback
beforeEach()
BeforeTestExecutionCallback
test()
AfterTestExecutionCallback
afterEach()
AfterEachCallback
afterAll()
AfterAllCallback
@BeforeAll
@BeforeEach
@BeforeEach
and before the test method.@ AfterEach
and after the test method.@ AfterEach
@ AfterAll
MyTestWatcher
package sample.junit5.extension;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import java.util.Optional;
public class MyTestWatcher implements TestWatcher, AfterEachCallback {
@Override
public void testDisabled(ExtensionContext context, Optional<String> reason) {
System.out.println("disabled : test=" + context.getDisplayName() + ", reason=" + reason.orElse("<empty>"));
}
@Override
public void testSuccessful(ExtensionContext context) {
System.out.println("successful : test=" + context.getDisplayName());
}
@Override
public void testAborted(ExtensionContext context, Throwable cause) {
System.out.println("aborted : test=" + context.getDisplayName() + ", cause=" + cause);
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
System.out.println("failed : test=" + context.getDisplayName() + ", cause=" + cause);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println("AfterEachCallback");
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestWatcher;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
@ExtendWith(MyTestWatcher.class)
class JUnit5Test {
@Test
void testSuccessful() {
System.out.println("testSuccessful()");
assertEquals(10, 10);
}
@Test
void testFailed() {
System.out.println("testFailed()");
assertEquals(10, 20);
}
@Test
@Disabled("REASON")
void testDisabled() {
System.out.println("testDisabled()");
}
@Test
void testAborted() {
System.out.println("testAborted()");
assumeTrue(false, "test abort");
}
}
Execution result
testAborted()
AfterEachCallback
aborted : test=testAborted(), cause=org.opentest4j.TestAbortedException: Assumption failed: test abort
testSuccessful()
AfterEachCallback
successful : test=testSuccessful()
disabled : test=testDisabled(), reason=REASON
testFailed()
AfterEachCallback
failed : test=testFailed(), cause=org.opentest4j.AssertionFailedError: expected: <10> but was: <20>
-TestWatcher can be used to implement processing according to the test result.
--The following four methods are defined in TestWatcher
- testDisabled()
--Run when the test was invalid
- testSuccessful()
--Run when the test is successful
- testAborted()
--Run when the test is interrupted (such as ʻassumeThat ()) -
testFailed() --Run when the test fails --Each method is defined as an empty
default method --Therefore, nothing is done by default --Write a concrete implementation by overriding each method as needed --Each method is executed after ʻAfterEachCallback
MyTestExecutionExceptionHandler
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
public class MyTestExecutionExceptionHandler implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
System.out.println(" * throwable=" + throwable);
if (throwable instanceof NullPointerException) {
throw throwable;
} else if (throwable instanceof IllegalStateException) {
throw new UnsupportedOperationException("test");
}
}
}
--If you receive a NullPointerException
, rethrow it as is
--Throw ʻUnsupportedOperationException if ʻIllegalStateException
is received
--Other than that, it ends without doing anything
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestExecutionExceptionHandler;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MyTestExecutionExceptionHandler.class)
class JUnit5Test {
@Test
void success() {
System.out.println("success()");
assertEquals(10, 10);
}
@Test
void fail() {
System.out.println("fail()");
assertEquals(10, 20);
}
@Test
void throwsIOException() throws Exception {
System.out.println("throwsIOException()");
throw new IOException("test");
}
@Test
void throwsNullPointerException() {
System.out.println("throwsNullPointerException()");
throw new NullPointerException("test");
}
@Test
void throwsIllegalStateException() {
System.out.println("throwsIllegalStateException()");
throw new IllegalStateException("test");
}
}
Execution result
success()
fail()
* throwable=org.opentest4j.AssertionFailedError: expected: <10> but was: <20>
throwsNullPointerException()
* throwable=java.lang.NullPointerException: test
throwsIllegalStateException()
* throwable=java.lang.IllegalStateException: test
throwsIOException()
* throwable=java.io.IOException: test
.
'-- JUnit Jupiter [OK]
+-- JUnit5Test [OK]
| +-- success() [OK]
| +-- fail() [OK]
| +-- throwsNullPointerException() [X] test
| +-- throwsIllegalStateException() [X] test
| '-- throwsIOException() [OK]
'-- ParallelismCheck [S] class sample.junit5.ParallelismCheck is @Disabled
Failures (2):
JUnit Jupiter:JUnit5Test:throwsNullPointerException()
MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'throwsNullPointerException', methodParameterTypes = '']
=> java.lang.NullPointerException: test
...
JUnit Jupiter:JUnit5Test:throwsIllegalStateException()
MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'throwsIllegalStateException', methodParameterTypes = '']
=> java.lang.UnsupportedOperationException: test
-TestExecutionExceptionHandler can be used to handle exceptions that occur in the test.
--When an exception is thrown in the test, handleTestExecutionException ()
is called.
--You can receive the exception thrown by the second argument
--The test fails if this method throws an exception
--If it exits without throwing any exceptions, the test succeeds (exceptions are squeezed)
--It is also possible to throw an exception other than the one received
--Be careful as assertion errors (ʻAssertionFailedError`) are also targeted.
――If you forget to re-throw, you will squeeze the assertion error.
MyTestExecutionExceptionHandler
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
public class MyTestExecutionExceptionHandler implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
System.out.println("[TestExecutionExceptionHandler] throwable=" + throwable);
}
}
MyLifecycleMethodExecutionExceptionHandler
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
public class MyLifecycleMethodExecutionExceptionHandler implements LifecycleMethodExecutionExceptionHandler {
@Override
public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
System.out.println("[LifecycleMethodExecutionExceptionHandler] throwable=" + throwable);
throw throwable;
}
@Override
public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
System.out.println("[LifecycleMethodExecutionExceptionHandler] throwable=" + throwable);
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyLifecycleMethodExecutionExceptionHandler;
import sample.junit5.extension.MyTestExecutionExceptionHandler;
class JUnit5Test {
@Nested
class InnerClass1 {
@Test
@DisplayName("If the lifecycle method normally throws an exception")
void test() {
System.out.println("InnerClass1");
}
}
@Nested
@ExtendWith(MyTestExecutionExceptionHandler.class)
class InnerClass2 {
@Test
@DisplayName("How TestExecutionExceptionHandler works for exceptions thrown in lifecycle methods")
void test() {
System.out.println("InnerClass2");
}
}
@Nested
@ExtendWith(MyLifecycleMethodExecutionExceptionHandler.class)
class InnerClass3 {
@Test
@DisplayName("When the exception thrown by the lifecycle method is squeezed by LifecycleMethodExecutionExceptionHandler")
void test() {
System.out.println("InnerClass3");
}
}
@Nested
@ExtendWith(MyLifecycleMethodExecutionExceptionHandler.class)
class InnerClass4 {
@BeforeEach
void beforeEach(TestInfo testInfo) {
throw new RuntimeException("beforeEach@" + simpleClassName(testInfo));
}
@Test
@DisplayName("If the LifecycleMethodExecutionExceptionHandler does not squeeze the exception thrown by the lifecycle method")
void test() {
System.out.println("InnerClass4");
}
}
@AfterEach
void afterEach(TestInfo testInfo) {
throw new RuntimeException("afterEach@" + simpleClassName(testInfo));
}
static String simpleClassName(TestInfo testInfo) {
return testInfo.getTestClass().map(Class::getSimpleName).orElse("<empty>");
}
}
Execution result
[LifecycleMethodExecutionExceptionHandler] throwable=java.lang.RuntimeException: beforeEach@InnerClass4
[LifecycleMethodExecutionExceptionHandler] throwable=java.lang.RuntimeException: afterEach@InnerClass4
InnerClass3
[LifecycleMethodExecutionExceptionHandler] throwable=java.lang.RuntimeException: afterEach@InnerClass3
InnerClass2
InnerClass1
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- InnerClass4 [OK]
| '--If the LifecycleMethodExecutionExceptionHandler does not squeeze the exception thrown by the lifecycle method[X] beforeEach@InnerClass4
+-- InnerClass3 [OK]
| '--When the exception thrown by the lifecycle method is squeezed by LifecycleMethodExecutionExceptionHandler[OK]
+-- InnerClass2 [OK]
| '--How TestExecutionExceptionHandler works for exceptions thrown in lifecycle methods[X] afterEach@InnerClass2
'-- InnerClass1 [OK]
'--If the lifecycle method normally throws an exception[X] afterEach@InnerClass1
-LifecycleMethodExecutionExceptionHandler can be used to handle exceptions raised by lifecycle methods.
--TestExecutionExceptionHandler
in the previous section can only be handled when an exception occurs in the test method body.
--TestExecutionExceptionHandler
is not called back when an exception is thrown in a lifecycle method
--Four methods are defined in LifecycleMethodExecutionExceptionHandler
--Each method corresponds to @BeforeAll
, @BeforeEach
, @AfterEach
, @AfterAll
, respectively.
--When an exception is thrown in a lifecycle method, the corresponding method is called.
--Each method is defined by the default method, and by default, the received exception is thrown again as it is.
--Same as for TestExecutionExceptionHandler
, you can squeeze the exception if you don't rethrow the exception received as an argument.
MyTestTemplateInvocationContextProvider
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import java.util.stream.Stream;
public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext context) {
System.out.println("[supportsTestTemplate] displayName=" + context.getDisplayName());
return context.getDisplayName().equals("test1()");
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
System.out.println("[provideTestTemplateInvocationContexts] displayName=" + context.getDisplayName());
return Stream.of(
new MyTestTemplateInvocationContext(),
new MyTestTemplateInvocationContext(),
new MyTestTemplateInvocationContext()
);
}
public static class MyTestTemplateInvocationContext implements TestTemplateInvocationContext {
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestTemplateInvocationContextProvider;
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
class JUnit5Test {
@TestTemplate
void test1() {
System.out.println("test1()");
}
@TestTemplate
void test2() {
System.out.println("test2()");
}
@Test
void test3() {
System.out.println("test3()");
}
}
Execution result
[supportsTestTemplate] displayName=test1()
[provideTestTemplateInvocationContexts] displayName=test1()
test1()
test1()
test1()
[supportsTestTemplate] displayName=test2()
test3()
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- test1() [OK]
| +-- [1] [OK]
| +-- [2] [OK]
| '-- [3] [OK]
+-- test2() [X] You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [void sample.junit5.JUnit5Test.test2()]
'-- test3() [OK]
Failures (1):
JUnit Jupiter:JUnit5Test:test2()
MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'test2', methodParameterTypes = '']
=> org.junit.platform.commons.PreconditionViolationException: You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [void sample.junit5.JUnit5Test.test2()]
...
--TestTemplateInvocationContextProvider allows you to execute the same test method multiple times in different contexts.
--Tests you want to run in different contexts need to be annotated with @TestTemplate
--supportsTestTemplate ()
of TestTemplateInvocationContextProvider
is called for each method for which @TestTemplate
is set.
--Implement to return true
when targeting (supporting) the target test
--If you have set @TestTemplate
but none of the supporting TestTemplateInvocationContextProvider
exists, you will get an error.
--If supported, provideTestTemplateInvocationContexts ()
will be called
--Implement this method to return Stream
of TestTemplateInvocationContext
--TestTemplateInvocationContext
represents one context when running a test
--Construct Stream
to return multiple elements when running in multiple contexts
--In the above implementation example, Stream
with 3MyTestTemplateInvocationContext
is returned, so the test1 ()
method is executed 3 times (in 3 contexts).
MyTestTemplateInvocationContextProvider
package sample.junit5.extension;
...
public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
...
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
System.out.println("[provideTestTemplateInvocationContexts] displayName=" + context.getDisplayName());
return Stream.of(
new MyTestTemplateInvocationContext("Hoge"),
new MyTestTemplateInvocationContext("Fuga"),
new MyTestTemplateInvocationContext("Piyo")
);
}
public static class MyTestTemplateInvocationContext implements TestTemplateInvocationContext {
private final String name;
public MyTestTemplateInvocationContext(String name) {
this.name = name;
}
@Override
public String getDisplayName(int invocationIndex) {
return this.name + "[" + invocationIndex + "]";
}
}
}
Execution result
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- test1() [OK]
| +-- Hoge[1] [OK]
| +-- Fuga[2] [OK]
| '-- Piyo[3] [OK]
:
--A method called getDisplayName ()
is defined in TestTemplateInvocationContext
.
--This method is defined by the default method, and the default implementation returns " [" + invocationIndex + "]"
.
--ʻInvocationIndex` is passed the index of the current context (starting with 1)
--By overriding this method, you can return any display name.
MyTestTemplateInvocationContextProvider
package sample.junit5.extension;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import java.util.List;
import java.util.stream.Stream;
public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
...
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
System.out.println("[provideTestTemplateInvocationContexts] displayName=" + context.getDisplayName());
return Stream.of(
new MyTestTemplateInvocationContext("BeforeEach", (BeforeEachCallback) ctx -> {
System.out.println("beforeEachCallback()");
}),
new MyTestTemplateInvocationContext("AfterEach", (AfterEachCallback) ctx -> {
System.out.println("afterEachCallback()");
})
);
}
public static class MyTestTemplateInvocationContext implements TestTemplateInvocationContext {
private final String name;
private final Extension extension;
public MyTestTemplateInvocationContext(String name, Extension extension) {
this.name = name;
this.extension = extension;
}
@Override
public String getDisplayName(int invocationIndex) {
return this.name;
}
@Override
public List<Extension> getAdditionalExtensions() {
return List.of(this.extension);
}
}
}
Execution result
...
beforeEachCallback()
test1()
test1()
afterEachCallback()
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- test1() [OK]
| +-- BeforeEach [OK]
| '-- AfterEach [OK]
:
--TestTemplateInvocationContext
defines a method calledgetAdditionalExtensions ()
--This method returns the extension to use in that context with List <Extension>
--This method is also the default method, and the default implementation returns an empty List <Extension>
.
--In the above implementation example, BeforeEachCallback
is set in the first context and ʻAfterEachCallbakis set in the second context. --By applying
ParameterResolver`, you can pass different parameters for each context and execute the same test method.
MyRegisterExtension
package sample.junit5.extension;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyRegisterExtension implements BeforeEachCallback, BeforeAllCallback {
private final String name;
public MyRegisterExtension(String name) {
this.name = name;
}
@Override
public void beforeAll(ExtensionContext context) throws Exception {
System.out.println("[" + this.name + "] beforeAll()");
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println("[" + this.name + "] beforeEach()");
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import sample.junit5.extension.MyRegisterExtension;
class JUnit5Test {
@RegisterExtension
static MyRegisterExtension classField = new MyRegisterExtension("classField");
@RegisterExtension
MyRegisterExtension instanceField = new MyRegisterExtension("instanceField");
@BeforeAll
static void beforeAll() {
System.out.println("beforeAll()");
}
@BeforeEach
void beforeEach() {
System.out.println("beforeEach()");
}
@Test
void test1() {
System.out.println("test1()");
}
}
Execution result
[classField] beforeAll()
beforeAll()
[classField] beforeEach()
[instanceField] beforeEach()
beforeEach()
test1()
--In the case of the method using @ExtendWith
, the adjustment of the extension class is basically static.
--The instance of the class that implements the extension is created behind the scenes by Jupiter.
--For this reason, it is basically impossible to make fine adjustments to instances of extension classes.
--On the other hand, if you use @RegisterExtension, you can dynamically adjust the extension class. Will be able to specify
--Declare the instance of the extension class you want to use as a field (static
or instance) of the test class you want to use the extension.
--By annotating this field with @RegisterExtension
, you can register the instance set in that field as an extension.
--Since the instance setting in the field can be described by any program [^ 7], you can use the freely adjusted instance.
--Any extension is available when using fields declared with static
--Class-level extensions like BeforeAllCallback
and instance-level extensions like TestInstancePostProcessor
are not available when using instance fields
--Ignored even if implemented
--Method-level extensions like BeforeEachCallback
are available
[^ 7]: You can simply generate it with a constructor, prepare a mechanism like a builder, or make it a factory method.
Folder structure
`-src/test/
|-java/
| `-sample/junit5/
| `-JUnit5Test.java
`-resources/
|-junit-platform.properties
`-META-INF/services/
`-org.junit.jupiter.api.extension.Extension
text:org.junit.jupiter.api.extension.Extension
sample.junit5.extension.MyServiceLoaderExtension
junit-platform.properties
junit.jupiter.extensions.autodetection.enabled=true
MyServiceLoaderExtension
package sample.junit5.extension;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyServiceLoaderExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println("MyServiceLoaderExtension.beforeEach()");
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
class JUnit5Test {
@Test
void test1() {
System.out.println("test1()");
}
}
Execution result
MyServiceLoaderExtension.beforeEach()
test1()
--Extensions can also be registered automatically using the ServiceLoader mechanism
--Create a / META-INF / services /
folder under the classpath and create a file named ʻorg.junit.jupiter.api.extension.Extensionin it. --In the file, describe the fully qualified name of the extension class to be registered. --When registering multiple items, list them separated by line breaks. --To enable auto-registration using ServiceLoader, you must specify
true for the configuration parameter
junit.jupiter.extensions.autodetection.enabled. --There is a way to specify it in the configuration file (
junit-platform.properties`), but you can also specify it in the system properties.
Consider how to refer to the information recorded when one Extension is executed when another Extension is executed.
For example, an image that records the start time of a test method with BeforeEachCallback
and outputs the execution time from the difference between the current time and the start time at ʻAfterEachCallback`.
If you're building your extension in a single class, you'll have a quick way to use instance variables.
MyStopwatch
package sample.junit5.extension;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyStopwatch implements BeforeEachCallback, AfterEachCallback {
private long startTime;
@Override
public void beforeEach(ExtensionContext context) throws Exception {
this.startTime = System.currentTimeMillis();
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
String displayName = context.getDisplayName();
long endTime = System.currentTimeMillis();
long time = endTime - this.startTime;
System.out.println("[" + displayName + "] time=" + time + " (startTime=" + this.startTime + ", endTime=" + endTime + ")");
}
}
Actually apply it to the following test and execute it.
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyStopwatch;
import java.util.concurrent.TimeUnit;
@ExtendWith(MyStopwatch.class)
class JUnit5Test {
@Test
void test1() throws Exception {
TimeUnit.MILLISECONDS.sleep(200);
}
@Test
void test2() throws Exception {
TimeUnit.MILLISECONDS.sleep(400);
}
@Test
void test3() throws Exception {
TimeUnit.MILLISECONDS.sleep(600);
}
}
Execution result
[test1()] time=211 (startTime=1577191073870, endTime=1577191074081)
[test2()] time=400 (startTime=1577191074126, endTime=1577191074526)
[test3()] time=602 (startTime=1577191074529, endTime=1577191075131)
It worked like that.
However, there are ** problems with this implementation **.
If you try to run this test in parallel, the problem becomes apparent.
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=2
junit.jupiter.execution.parallel.mode.default=concurrent
Execution result
[test1()] time=214 (startTime=1577191442987, endTime=1577191443201)
[test3()] time=363 (startTime=1577191443234, endTime=1577191443597)
[test2()] time=402 (startTime=1577191443234, endTime=1577191443636)
The execution time of test3 ()
is about 300ms (actually, this value is impossible because it sleeps for 600ms).
If you look closely, you can see that startTime
oftest3 ()
andtest2 ()
have the same value.
This means that the startTime
used to timetest3 ()
has become the start time oftest2 ()
.
The cause of this problem is that the startTime
is shared by the instance variable of MyStopwatch
.
MyStopwatch
is using the same instance while the JUnit5Test
test is running.
In other words, the MyStopwatch
used when each test method is executed is all the same instance.
When test3 ()
is executed, beforeEach ()
records the start time in startTime
.
However, immediately after that, test2 ()
is executed in parallel, and the value of startTime
is overwritten by the start time oftest2 ()
.
As a result, the above-mentioned problems have occurred.
In this way, using instance variables of the implementation class for data sharing between Extensions can cause unexpected problems depending on how the test is executed. (There may be other patterns that cause problems, but for the time being, the only case I can think of is this parallel execution case.)
I don't know if it's meant to solve this problem, but Store
allows you to implement data sharing so that it doesn't cause problems when run in parallel.
MyStopwatch
package sample.junit5.extension;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyStopwatch implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
store.put("startTime", System.currentTimeMillis());
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
long startTime = store.get("startTime", long.class);
long endTime = System.currentTimeMillis();
long time = endTime - startTime;
String displayName = context.getDisplayName();
System.out.println("[" + displayName + "] time=" + time + " (startTime=" + startTime + ", endTime=" + endTime + ")");
}
}
Execution result
[test1()] time=213 (startTime=1577193397891, endTime=1577193398104)
[test3()] time=609 (startTime=1577193397891, endTime=1577193398500)
[test2()] time=401 (startTime=1577193398142, endTime=1577193398543)
The time of test3 ()
is about 600ms and it works well.
(StartTime
is the same astest1 ()
becausetest1 ()
andtest3 ()
are started at the same time with 2 parallels, so there is no problem)
This implementation uses a mechanism called Store.
Store
is a container of data prepared for each ʻExtensionContext`, and arbitrary data can be saved in Key-Value format.
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
store.put("startTime", System.currentTimeMillis());
...
long startTime = store.get("startTime", long.class);
An instance of Store
is ʻExtensionContext`'s [getStore (Namespace)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html#getStore(org) It can be obtained by the .junit.jupiter.api.extension.ExtensionContext.Namespace)) method.
Specify Namespace as an argument.
As an image, multiple Store
s are stored in ʻExtensionContext, and it feels like
Namespacespecifies which
Storeto get. (Actually,
Namespace is only used as part of the key, and the entity of
Storeis implemented by a single
Map`, but I think this is fine as an image)
By separating Store
in Namespace
in this way, even if there are multiple extensions that use the same key, Store
can be separated and data can be shared.
By the way, if you want to share data with all extensions, [Namespace.GLOBAL](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace You can also use a predefined constant called .html # GLOBAL).
[Create (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace.html#create to create Namespace
(java.lang.Object ...)) Use the method.
Any object can be specified as an argument, but it must be an object that can be compared and verified with the ʻequals () `method.
The life cycle of the Store matches the source ʻExtensionContext`.
MyExtension
package sample.junit5.extension;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
System.out.println("[beforeAll]");
this.printStoreValues(context);
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("foo"));
store.put("hoge", "INITIAL VALUE");
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println("[beforeEach@" + context.getDisplayName() + "]");
this.printStoreValues(context);
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("foo"));
store.put("hoge", context.getDisplayName());
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println("[afterEach@" + context.getDisplayName() + "]");
this.printStoreValues(context);
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
System.out.println("[afterAll]");
this.printStoreValues(context);
}
private void printStoreValues(ExtensionContext context) {
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("foo"));
System.out.println(" hoge=" + store.get("hoge"));
System.out.println(" context.class=" + context.getClass().getCanonicalName());
}
}
--In beforeAll ()
and beforeEach ()
, first output the information of Store
and then set the value of hoge
.
--And, ʻafterEach () and ʻafterAll ()
output the information of Store
as it is.
--Each, the class name of ʻExtensionContext` is also output.
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExtension;
@ExtendWith(MyExtension.class)
class JUnit5Test {
@Test
void test1() throws Exception {}
@Test
void test2() throws Exception {}
}
Execution result
[beforeAll]
hoge=null
context.class=org.junit.jupiter.engine.descriptor.ClassExtensionContext
[beforeEach@test1()]
hoge=INITIAL VALUE
context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext
[afterEach@test1()]
hoge=test1()
context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext
[beforeEach@test2()]
hoge=INITIAL VALUE
context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext
[afterEach@test2()]
hoge=test2()
context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext
[afterAll]
hoge=INITIAL VALUE
context.class=org.junit.jupiter.engine.descriptor.ClassExtensionContext
--In class-level extensions like BeforeAllCallback
and ʻAfterAllCallback,
ClassExtensionContext is passed as ʻExtensionContext
.
--On the other hand, method-level extensions like BeforeEachCallback
and ʻAfterEachCallback pass
MethodExtensionContext. --In this way, ʻExtensionContext
is passed an implementation instance that matches the level at which the extension is running.
--These ʻExtensionContexthave a parent-child relationship, and the parent of
MethodExtensionContext is
ClassExtensionContext. --If you follow the parent's context, you will end up with
JupiterEngineExtensionContext. --The parent context is ʻExtensionContext
's [getParent ()](https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getParent- -) Can be obtained by method
--JupiterEngineExtensionContext
is the context of the most parent (root)
--The root context is ʻExtensionContext [getRoot ()](https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getRoot- -) Can be obtained by method --In addition, there are other implementation classes of ʻExtensionContext
such as TestTemplateExtensionContext
and DynamicExtensionContext
, but they are omitted here.
--Store
is kept for each instance of these contexts
--MethodExtensionContext
is generated for each test method
--That is, the MethodExtensionContext
oftest1 ()
andtest2 ()
passes different instances.
――Therefore, Store
is also different.
--As a result, the information saved in Store
bytest1 ()
cannot be referenced bytest2 ()
.
--Thanks to this, MyStopwatch
was able to avoid problems during parallel execution by using Store
.
--BeforeEachCallback
and ʻAfterEachCallbackare method-level extensions --Since the context is different for each test method to be executed,
Storeis also different. ――For this reason, even if they are executed in parallel, the data does not conflict and it can operate normally. --Information stored in the parent context
Store can also be referenced from the child context
Store. -And it can be overwritten in the child context --However, the overwritten information returns to the original value set in the parent context when the scope of the child context ends and it moves to another context. --The value set in
test1 ()returns to the original
" INITIAL VALUE " at the stage of
beforeEach () and ʻafterAll ()
of test2 ()
.
--This behavior is achieved by recursively searching the parent Store
if the specified key information does not exist in Store
.
--In other words, if it does not exist in the Store
of the MethodExtensionContext
, it will search for the Store
of the parent ClassExtensionContext
by tracing to the root Store
if it is not there.
--This makes it possible to refer to the value set in the parent context from test1 ()
.
--However, even if you set the value to Store
in the child context, the Store
in the parent context remains the same, so when the child context ends, the information in the parent context is restored.
--If you draw the relationship between ʻExtensionContext and
Store` in a diagram, it looks like the one below.
MyCloseableResource
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyCloseableResource implements ExtensionContext.Store.CloseableResource {
private final String name;
public MyCloseableResource(String name) {
this.name = name;
}
@Override
public void close() throws Throwable {
System.out.println(" Close Resource > " + this.name);
}
}
MyExtension
package sample.junit5.extension;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyExtension implements BeforeEachCallback, BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
MyCloseableResource resource = new MyCloseableResource("BeforeAll");
context.getStore(ExtensionContext.Namespace.GLOBAL).put("foo", resource);
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
MyCloseableResource resource = new MyCloseableResource("BeforeEach(" + context.getDisplayName() + ")");
context.getStore(ExtensionContext.Namespace.GLOBAL).put("foo", resource);
}
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExtension;
@ExtendWith(MyExtension.class)
class JUnit5Test {
@Test
void test1() throws Exception {
System.out.println("test1()");
}
@Test
void test2() throws Exception {
System.out.println("test2()");
}
@AfterAll
static void afterAll() {
System.out.println("afterAll()");
}
}
Execution result
test1()
Close Resource > BeforeEach(test1())
test2()
Close Resource > BeforeEach(test2())
afterAll()
Close Resource > BeforeAll
--An instance that implements CloseableResource is saved in Store
If so, at the end of the Store
lifecycle [close ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext. Store.CloseableResource.html # close ()) method is called automatically
MyParameterResolver
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
public class MyParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType().equals(String.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return extensionContext.getRequiredTestMethod().getName();
}
}
MyParameterResolverExtension
package sample.junit5.extension;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith(MyParameterResolver.class)
public @interface MyParameterResolverExtension {}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import sample.junit5.extension.MyParameterResolverExtension;
@MyParameterResolverExtension
class JUnit5Test {
@Test
void test1(String testMethodName) {
System.out.println("[test1] testMethodName=" + testMethodName);
}
@Test
void test2(String testMethodName) {
System.out.println("[test2] testMethodName=" + testMethodName);
}
}
Execution result
[test1] testMethodName=test1
[test2] testMethodName=test2
--JUnit Jupiter supports the meta-annotation mechanism
--In other words, you can define another self-made annotation that is a collection of arbitrary annotations.
--If you use the same combination of annotations in multiple places, you can combine them into a single self-made annotation to improve reusability.
--Rather than directly specifying the Class
object with @ExtendWith
for the extension, there may be an advantage that you can separate it from the concrete implementation by inserting your own annotation.
To adjust test behavior such as junit.jupiter.testinstance.lifecycle.default
to change the default lifecycle of a test instance and junit.jupiter.execution.parallel.enabled
to enable parallel execution. The parameters are called ** configuration parameters **.
The following three methods are available for specifying the setting parameters.
junit-platform.properties
The method provided for each first Launcher is specified by the --config
option when using ConsoleLauncher, for example.
Gradle doesn't seem to currently support specifying this way (instead it uses system properties or junit-platform.properties
).
For Maven, specify with the configurationParameters
property.
The second JVM system property is specified as it is in the system property (the specification method differs depending on each Launcher).
The third is enabled by placing a property file called junit-platform.properties
at the root of the classpath.
If the same key configuration parameters are specified in these different ways, the value defined above takes precedence.
That is, the value provided for each Launcher has the highest priority, and the value defined in junit-platform.properties
has the lowest priority.
Actually, try it with ConsoleLauncher.
MyExtension
package sample.junit5.extension;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class MyExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
this.printConfigurationParameter(context, "hoge");
this.printConfigurationParameter(context, "fuga");
this.printConfigurationParameter(context, "piyo");
}
private void printConfigurationParameter(ExtensionContext context, String key) {
System.out.println("key=" + key + ", value=" + context.getConfigurationParameter(key).orElse("<empty>"));
}
}
--The value of the configuration parameter can be referenced by getConfigurationParameter ()
of ʻExtensionContext. --Here, I try to output the values of the three setting parameters
hoge,
fuga, and
piyo`.
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExtension;
@ExtendWith(MyExtension.class)
class JUnit5Test {
@Test
void test1() {
System.out.println("test1()");
}
}
junit-platform.properties
hoge=HOGE@properties
fuga=FUGA@properties
piyo=PIYO@properties
--hoge
, fuga
, piyo
defines everything
> java -Dfuga=FUGA@SystemProperty ^
-Dpiyo=PIYO@SystemProperty ^
-jar junit-platform-console-standalone-1.5.2.jar ^
--config piyo=PIYO@ConfigOption ^
...
--fuga
and piyo
are specified in the JVM system properties
--Only piyo
is also specified in the --config
option.
Execution result
key=hoge, value=HOGE@properties
key=fuga, value=FUGA@SystemProperty
key=piyo, value=PIYO@ConfigOption
test1()
The table below shows the relationship between each key and the value specified for each setting method.
(The value with *
is the value finally adopted)
Key | junit-platform.properties | JVM system properties | --config |
---|---|---|---|
hoge |
HOGE@properties * |
||
fuga |
FUGA@properties |
FUGA@SystemProperty * |
|
piyo |
PIYO@properties |
PIYO@SystemProperty |
PIYO@ConfigOption * |
You can see that the value specified by --config
has priority and the value specified by junit-platform.properties
has lower priority.
JUnit5 provides a class for assertions called Assertions.
Here you will find assertEquals () are provided. Also, assertTimeout () and [assertAll ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertAll (org.junit.jupiter) There are also some useful methods like .api.function.Executable ...)).
However, the impression is that only the minimum necessary items are prepared.
JUnit5 allows you to use any third-party assertion library. In other words, if you add AssertJ or Hamcrest to the dependent libraries, you can add it to JUnit4. Can be used normally.
The following is an example of adding AssertJ to a dependency.
build.gradle
dependencies {
...
testImplementation "org.assertj:assertj-core:3.11.1"
}
JUnit5Test
package sample.junit5;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class JUnit5Test {
@Test
void test() throws Exception {
assertThat(10).isEqualTo(8);
}
}
Execution result
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- test() [X]
Expecting:
<10>
to be equal to:
<8>
but was not.
...
In reality, I think I'll be using these third-party assertions, so I'll omit the use of standard assertions.
Recommended Posts