Inutile de dire que la dernière version majeure du framework de test Java à partir de 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"
}
--Si vous voulez commencer à utiliser JUnit5 pour le moment, spécifiez ʻorg.junit.jupiter: junit-jupiter` comme dépendance ( détails ci-dessous ).
organisation des fichiers
|-build.gradle
`-src/test/java/
`-sample/junit5/
|-JUnit5Test.java
|-JUnit5Tests.java
|-TestJUnit5.java
`-Hoge.java
――Nous avons 4 types de classes de test
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);
}
}
}
--Chaque classe définit uniquement la méthode fail ()
qui échoue toujours
--Deux classes imbriquées statiques, StaticClass
et StaticTest
,
Définition de la classe ʻInnerTest` comme classe interne
JUnit5Test.java
, mais les trois autres ( JUnit5Tests.java
, TestJUnit5.java
, Hoge.java
) ont la même implémentation du contenu.Un outil permettant d'exécuter JUnit5 sur la ligne de commande est ** ConsoleLauncher **. La substance est un fichier jar dans lequel chaque module de JUnit5 est consolidé en un seul. Maven's Central Repository Télécharger.
Ici, téléchargez et vérifiez junit-platform-console-standalone-1.5.2.jar
.
#Compiler
> gradle compileTestJava
#Courir
> 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 ]
--Ajoutez la dépendance (autre que JUnit5) utilisée dans le test au chemin de classe avec l'option -cp
.
junit-platform-console-standalone-1.5.2.jar
-- - scan-classpath
pour spécifier où trouver la classe de test que vous souhaitez exécuter> 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?)$]
...
Hoge$StaticTest
JUnit5Test
JUnit5Test$StaticTest
JUnit5Tests
JUnit5Tests$StaticTest
TestJUnit5
TestJUnit5$StaticClass
TestJUnit5$StaticTest
Hoge
Hoge$InnerTest
Hoge$StaticClass
JUnit5Test$InnerTest
JUnit5Test$StaticClass
JUnit5Tests$InnerTest
JUnit5Tests$StaticClass
TestJUnit5$InnerTest
--Spécifiez la condition de recherche de classe de test avec l'option -n
ou --include-classname
(spécification d'expression régulière)
-La valeur par défaut est^(Test.*|.+[.$]Test.*|.*Tests?)$
@ Nested
est requis)public
Mais je pense que cela se fait généralement à partir de l'outil de construction que vous utilisez. Gradle a un support natif pour exécuter JUnit5 depuis 4.6, alors essayez de l'exécuter à partir de Gradle.
build.gradle
...
test {
useJUnitPlatform() //★ Ajout
}
--Gradle prend également en charge d'autres frameworks de test tels que JUni4 et TestNG
test {useJUnitPlatform ()}
Lancer le 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
--Les classes suivantes ont été exécutées
- Hoge
*
- Hoge$StaticClass
*
- Hoge$StaticTest
- JUnit5Test
- JUnit5Test$StaticClass
*
- JUnit5Test$StaticTest
- JUnit5Tests
- JUnit5Tests$StaticClass
*
- JUnit5Tests$StaticTest
- TestJUnit5
- TestJUnit5$StaticClass
- TestJUnit5$StaticTest
--Plus de classes sont exécutées que lorsqu'elles sont exécutées avec ConsoleLauncher (plus de classes sont marquées d'un *)
--Si vous exécutez le test à partir de Gradle, la valeur par défaut est la propriété testClassesDirs de la tâche Test (https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle Toutes les classes qui existent à l'emplacement spécifié par .api.tasks.testing.Test: testClassesDirs) sont ciblées.
sourceSets.test
.src / test / java
sont ciblées (qu'elles commencent ou non par Test
etc.)
--Si vous souhaitez affiner la cible, [inclut](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing. Test: inclut) et exclut
--Cependant, la classe interne (ʻInnerTest`) est exclue par défaut, ce qui est identique à ConsoleLauncher.La version confirmée est «2019-09 Standard Edition» de Pléiades.
Pour l'exécuter, cliquez-droit sur le dossier src / test / java
et sélectionnez" Exécuter "->" JUnit Test ".
«Hoge» et «StaticClass» ont également été ciblés.
La version confirmée est la version communautaire 2019.2.3.
Pour IDEA, si vous l'ouvrez en tant que projet Gradle, la tâche Gradle test
exécutera le test.
Le comportement dans ce cas est donc le même que lors de l'exécution de Gradle.
Vous pouvez également spécifier la configuration d'exécution à partir de [Configurations Exécuter / Déboguer] sans utiliser Gradle. Cependant, dans ce cas, cela dépend de la spécification de "Type de test".
JUnit5 se compose de trois modules principaux (sous-projets).
JUnit Platform
La base pour exécuter le framework de test sur la JVM. Il fournit un mécanisme pour exécuter un module qui implémente une interface appelée TestEngine.
Il fournit également ConsoleLauncher
etc. pour le lancement depuis la console.
JUnit Jupiter
Un module pour créer et exécuter des tests JUnit 5. Fournit JupiterTestEngine qui implémente «TestEngine» pour JUnit 5.
Vous pouvez considérer Jupiter comme JUnit 5.
JUnit Vintage
Une classe qui implémente TestEngine
pour exécuter JUnit 3 et 4 sur la plate-forme JUnit- [VintageTestEngine](https://junit.org/junit5/docs/current/api/org/junit/vintage/engine/VintageTestEngine. Un module qui fournit du html).
Je pense qu'il a été préparé pour la compatibilité transitoire, donc ce n'est pas nécessaire si vous introduisez un nouveau JUnit 5.
JUnit 5 se compose des deux modules suivants.
--Plateforme pour exécuter le framework de test --Module de cadre de test spécifique (Jupiter, Vintage)
La plate-forme est requise pour exécuter le test. Jupiter est nécessaire lorsque vous souhaitez écrire des tests dans JUnit 5, et Vintage est nécessaire lorsque vous souhaitez exécuter JUnit 4 sur la plate-forme JUnit.
Les modules tels que Platform et Jupiter correspondent au Groupe dans la classification de Maven.
Dans chaque groupe, il y a plusieurs autres artefacts.
** Dépendances d'artefacts dans chaque module **
Lors de l'écriture d'un test JUnit 5, vous devez sélectionner correctement uniquement ceux dont vous avez besoin parmi ces artefacts et les ajouter à vos dépendances. Comme vous pouvez le voir sur la figure ci-dessus, il est difficile à première vue de déterminer lequel est nécessaire.
Par conséquent, dans la version 5.4.0, un artefact appelé junit-jupiter a été ajouté au groupe «org.junit.jupiter».
** Figure avec junit-jupiter ajoutée **
junit-jupiter
est un artefact qui résume uniquement les dépendances minimales requises pour écrire des tests dans JUnit 5.
Donc, si vous souhaitez simplement écrire des tests dans JUnit 5, il vous suffit d'ajouter un de ces artefacts à vos dépendances.
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);
}
}
-La méthode annotée par @Test devient la méthode de test. --Le package est différent de org.junit.Test jusqu'à JUnit4. --Il semble être capable de mélanger à la fois 4 et 5 tests pendant la période de transition à 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()");
}
}
Résultat d'exécution
JUnit5Test#beforeAll()
JUnit5Test#beforeEach()
JUnit5Test#test1()
JUnit5Test#afterEach()
JUnit5Test#beforeEach()
JUnit5Test#test2()
JUnit5Test#afterEach()
JUnit5Test#afterAll()
-Les méthodes avec @BeforeAll ne sont qu'une seule fois au tout début de la classe de test. Sera exécuté
package sample.junit5;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
@DisplayName("En classe")
class JUnit5Test {
@Test
@DisplayName("Réussir")
void success() {
Assertions.assertEquals(10, 10);
}
@Test
@DisplayName("En échec")
void fail() {
Assertions.assertEquals(10, 8);
}
}
** Lorsqu'il est exécuté avec 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]
| '--En classe[OK]
| +--Réussir[OK]
| '--En échec[X] expected: <10> but was: <8>
'-- JUnit Vintage [OK]
Failures (1):
JUnit Jupiter:En classe:En échec
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 ]
** Lorsqu'il est exécuté sur 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
** Rapport HTML **
** Rapport XML **
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>
...
@ DisplayName
à une classe ou une méthode, vous pouvez spécifier le nom du test sous forme de chaîne de caractères arbitraire.
――Cependant, le rapport de Gradle semble apte à correspondrepackage 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()");
}
}
}
Résultat d'exécution
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()
--Annoter une classe interne non "statique" avec @Nested et imbriquer des classes de test Peut être
--Parce qu'il ne doit pas être «statique», «@ BeforeAll» et «@ AfterAll» ne peuvent pas être spécifiés car ils sont [^ 2]
--Si vous voulez vraiment le spécifier, vous devez définir PER_CLASS
en spécifiant tester le cycle de vie de l'instrument ci-dessous.
[^ 2]: @ BeforeAll
, @ AfterAll
doivent être spécifiés comme des méthodes static
, mais les classes internes non static
ne peuvent pas définir de méthodes static
selon les spécifications du langage Java.
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()");
}
}
Résultat d'exécution
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 ]
-Si vous utilisez Assumptions.assumeTrue (boolean), l'argument est ` Les tests suivants s'exécutent uniquement lorsque vous réussissez true --S'il est «faux», le reste du traitement dans la méthode de test est interrompu.
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()");
}
}
Résultat d'exécution
test1()
-Les méthodes de test avec @Disabled ne seront plus exécutées --Peut être attaché à une classe (dans ce cas, toutes les méthodes de test de la classe de test ne seront pas exécutées)
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");
}
}
Résultat d'exécution
enabled on windows
disabled on mac
-Avec @EnabledOnOs, vous ne pouvez activer le test que sur un système d'exploitation spécifique.
-Avec @DisabledOnOs, vous ne pouvez désactiver le test que sur un système d'exploitation spécifique.
--Pour value
, spécifiez la constante définie dans 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");
}
}
Résultat d'exécution
enabled on java 11
disabled on java 12
-Ajoutez @EnabledOnJre pour activer les tests uniquement sur des versions Java spécifiques. ça peut
-Ajoutez @DisabledOnJre pour désactiver les tests uniquement sur certaines versions de Java. ça peut
--Pour value
, spécifiez la constante définie dans 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");
}
}
Résultat d'exécution
enabled if AdoptOpenJDK
disabled if Oracle
-Si @EnabledIfSystemProperty est ajouté, le test sera effectué en fonction de la valeur de la propriété système. Peut être activé
-Si @DisabledIfSystemProperty est ajouté, le test sera effectué en fonction de la valeur de la propriété système. Peut être désactivé
--Spécifiez le nom de la propriété système que vous souhaitez conditionner dans named
--Spécifiez la valeur conditionnelle dans matches
avec une expression régulière (correspondance complète)
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");
}
}
Résultat d'exécution
enabled if AdoptOpenJDK
disabled if OpenJDK
-Si @EnabledIfEnvironmentVariable est ajouté, le test sera effectué en fonction de la valeur de la variable d'environnement. Peut être activé
-Si @DisabledIfEnvironmentVariable est ajouté, le test sera effectué en fonction de la valeur de la variable d'environnement. Peut être désactivé
--Spécifiez le nom de la variable d'environnement que vous souhaitez conditionner dans named
--Spécifiez la valeur conditionnelle dans matches
avec une expression régulière (correspondance complète)
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)");
}
}
** Lorsqu'il est exécuté normalement **
Résultat d'exécution
> 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)
** - Lors de la réduction par include-tag **
Résultat d'exécution
> 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)
-Vous pouvez baliser les cas de test en attachant @Tag aux classes et méthodes de test.
null
ou de caractère vide(
- )
- ,
- &
- |
- !
- include-tag
ne peut cibler que les balises qui correspondent aux conditions
-- - exclude-tag
ne peut cibler que les balises qui ne correspondent pas aux conditionsbuild.gradle
...
test {
useJUnitPlatform {
includeTags "foo | !fizz"
}
}
!
:NOT&
:AND|
:OR
--Il est également possible de résumer les conditions avec des parenthèses ()
Résultat d'exécution
> 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");
}
}
Résultat d'exécution
ant
cat
dog
bear
Par défaut, l'ordre d'exécution des méthodes de test est déterminé intentionnellement par un algorithme non trivial [^ 1]. En effet, il est souhaitable que les tests unitaires ne dépendent pas de l'ordre d'exécution.
[^ 1]: Bref, je pense que c'est un ordre qui n'est pas aléatoire mais qui ne peut pas être deviné.
Cependant, dans le cas des tests d'intégration et des tests fonctionnels, l'ordre d'exécution peut être important. [^ 8]
[^ 8]: Après vous être enregistré en tant que maître, déplacez les fonctions individuelles (probablement)
À de tels moments, un mécanisme est fourni pour contrôler l'ordre d'exécution du test.
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");
}
}
Résultat d'exécution
ant
bear
cat
dog
--Définissez une annotation pour la classe de test @TestMethodOrder
--Pour value
, spécifiez l'objet Class
de la classe qui implémente MethodOrderer. Faire
--MethodOrder
offre la possibilité de contrôler l'ordre dans lequel les méthodes sont exécutées.
--Il existe trois implémentations standard de MethodOrder
.
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");
}
}
Résultat d'exécution
dog
cat
bear
ant
-Utilisation de OrderAnnotation, [@Order](https: // junit. org / junit5 / docs / current / api / org / junit / jupiter / api / Order.html) Vous pouvez spécifier la commande avec une annotation
@ Order
sont affectées par défaut à ʻInteger.MAX_VALUE`.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");
}
}
Résultat d'exécution
#1ère fois
ant
dog
cat
bear
#Deuxième fois
bear
ant
cat
dog
-Si Random est spécifié, l'ordre d'exécution des méthodes sera aléatoire.
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());
}
}
Résultat d'exécution
before@278240974
test1@278240974
before@370370379
test2@370370379
before@671046933
test3@671046933
--Par défaut, une nouvelle instance de classe de test est créée chaque fois que la méthode de test est exécutée.
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());
}
}
Résultat d'exécution
before@1504642150
test1@1504642150
before@1504642150
test2@1504642150
before@1504642150
test3@1504642150
--Annotez la classe de test avec @TestInstance et définissez value
sur [PER_CLASS]( Spécifiez https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestInstance.Lifecycle.html#PER_CLASS)
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()");
}
}
Résultat d'exécution
beforeAll()
staticBeforeAll()
test()
staticAfterAll()
afterAll()
--Il est également possible de le laisser attaché à la méthode statique
@ BeforeAll
et @ AfterAll
pour tester les classes imbriquées avec @ Nested
.
--Si vous ne spécifiez pas PER_CLASS
, @ BeforeAll
, @ AfterAll
doit rendre la méthode statique
.@ Nested
est une classe interne, la méthode static
ne peut pas être définie en raison des spécifications du langage Java.@ Nested
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() *");
}
}
}
Résultat d'exécution
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()
--Il est maintenant possible de définir le premier et le dernier traitement uniquement dans @ Nested
.
--Par défaut, une instance de test est créée pour chaque méthode de test
-Djunit.jupiter.testinstance.lifecycle.default = per_class
--Lors de la spécification dans le fichier de configuration de JUnit Platform, placez d'abord le fichier junit-platform.properties
dans la racine du chemin de classe.
--Et spécifiez junit.jupiter.testinstance.lifecycle.default = per_class
dans ce fichier de propriétéspackage sample.junit5;
import org.junit.jupiter.api.RepeatedTest;
class JUnit5Test {
@RepeatedTest(3)
void test() {
System.out.println("test");
}
}
** Résultat d'exécution (pour ConsoleLauncher) **
Résultat d'exécution(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 ]
** Résultat d'exécution (pour Gradle) **
-Lorsque vous annotez une méthode avec @RepeatedTest, le test sera effectué le nombre de fois spécifié par value
. Sera répété
répétition <nombre actuel de répétitions> de <nombre total de répétitions>
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");
}
}
** Résultat de l'exécution (pour le lanceur de console) **
Résultat d'exécution(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]
** Résultat d'exécution (pour Gradle) **
name
.displayName
: nom d'affichage de la méthode de test@ DisplayName
est spécifié, il sera utilisé.
--currentRepetition
: nombre actuel de répétitions (à partir de 1)
--totalRepetitions
: nombre total de répétitions@ RepetedTest
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);
}
}
Résultat d'exécution
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)
@ RepeatedTest
contient les informations d'itération en cours [RepetitionInfo](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/RepetitionInfo] .html) Peut accepter des objets comme arguments@ BeforeEach
, @ AfterEach
@ Test
est mélangée ici, RepetitionInfo
ne peut pas être résolu et une erreur d'exécution se produira.getCurrentRepetition ()
(1 start)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);
}
}
** Résultat de l'exécution (pour le lanceur de console) **
Résultat d'exécution(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 ]
** Résultat d'exécution (pour Gradle) **
-La méthode annotée par @ParameterizedTest reçoit la valeur utilisée dans le test comme argument. Sera exécuté pendant
--Il existe plusieurs façons de déclarer la valeur à passer à l'argument, mais ici [@ValueSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ ValueSource.html) à l'aide d'annotations
-- @ ValueSource
peut déclarer statiquement les paramètres à passer aux méthodes de test avec des attributs tels que strings
et ʻints --La source qui génère le paramètre (
@ ValueSource` dans l'exemple de ↑) est appelée le paramètre ** 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
}
}
Résultat d'exécution
value=HOGE
value=FUGA
value=PIYO
Peut être utilisé comme paramètre --Si vous passez un objet
Class de ʻenum
à value
, toutes les constantes définies seront utilisées comme paramètres.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
}
}
Résultat d'exécution
value=HOGE
value=PIYO
-Vous pouvez affiner les constantes utilisées dans noms
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
}
}
Résultat d'exécution
value=FUGA
-En mode, laquelle condition est spécifiée par names
Vous pouvez spécifier si vous souhaitez postuler
-Si EXCLUDE est spécifié, spécifiez-le avec names
. Est exclu
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
}
}
Résultat d'exécution
matchAll() value=FOURTH
matchAll() value=FIFTH
matchAny() value=FIRST
matchAny() value=FOURTH
matchAny() value=FIFTH
matchAny() value=SIXTH
MATCH_ALL
peut être réduit aux seules constantes qui correspondent à toutes les conditions
--MATCH_ANY
peut être réduit aux seules constantes qui correspondent à l'une des conditionspackage 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");
}
}
Résultat d'exécution
value=hoge
value=fuga
value=piyo
statique
avec le même nom que la méthode de test et sans argument est automatiquement sélectionnée comme source.Stream
, DoubleStream
, LongStream
, ʻIntStream,
Collection, ʻIterator
, ʻIterable`, tableau d'objets, tableau de types primitifs
«Ainsi, la plupart des ** types semblables ** se sentent bien.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");
}
}
Résultat d'exécution
value=HOGE
value=FUGA
value=PIYO
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);
}
}
Résultat d'exécution
value=foo
value=bar
--Dans value
of @ MethodSource
, vous pouvez utiliser la méthode de la classe externe comme source en spécifiant le nom complet de la classe #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)
);
}
}
Résultat d'exécution
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
Stream
(ce qui peut être) de ʻObject []. --Dans cet exemple,
List <Object []>` est retourné.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);
}
}
Résultat d'exécution
s1=[foo], s2=[bar], i=[1]
s1=[hoge,fuga], s2=[], i=[2]
s1=[fizz], s2=[null], i=[3]
-Utilisez @CsvSource pour générer du texte au format CSV statique ça peut
--Utilisez des guillemets simples (`` '') pour les guillemets
--''
est traité comme un caractère vide et un blanc complet est traité comme "nul".
Taxe d'hommage de dossier
`-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);
}
}
Résultat d'exécution
string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]
-Utilisez @CsvFileSource pour spécifier le fichier CSV dans le chemin de la classe comme source ça peut
--Spécifiez le chemin du fichier CSV à utiliser avec resources
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);
}
}
Résultat d'exécution
string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]
-Utilisation de @ArgumentsSource, [ArgumentsProvider](https: // junit. Une classe qui implémente org / junit5 / docs / current / api / org / junit / jupiter / params / provider / ArgumentsProvider.html) peut être utilisée comme source.
--Spécifiez un objet Class
d'une classe qui implémente ʻArgumentsProvider dans
value`
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));
}
}
Résultat d'exécution
i=10, l=20, d=30.0
byte-> short
ou ʻint-> long`, l'argument de la méthode de test peut être reçu par
long ou
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
}
}
Résultat d'exécution
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
Type converti | Méthode de conversion |
---|---|
boolean /java.lang.Boolean |
Boolean.valueOf(String)[^4] |
char /java.lang.Character |
String.charAt(0)[^3] |
byte /java.lang.Byte |
Byte.decode(String) |
short /java.lang.Short |
Short.decode(String) |
int /java.lang.Integer |
Integer.decode(String) |
long /java.lang.Long |
Long.decode(String) |
float /java.lang.Float |
Float.valueOf(String) |
double /java.lang.Double |
Double.valueOf(String) |
toutenum Type (java.lang.Enum Sous-classe) |
Enum.valueOf(Class, String) |
java.time.Duration |
Duration.parse(CharSequence) |
java.time.Instant |
Instant.parse(CharSequence) |
java.time.LocalDate |
LocalDate.parse(CharSequence) |
java.time.LocalDateTime |
LocalDateTime.parse(CharSequence) |
java.time.LocalTime |
LocalTime.parse(CharSequence) |
java.time.MonthDay |
MonthDay.parse(CharSequence) |
java.time.OffsetDateTime |
OffsetDateTime.parse(CharSequence) |
java.time.OffsetTime |
OffsetTime.parse(CharSequence) |
java.time.Period |
Period.parse(CharSequence) |
java.time.Year |
Year.parse(CharSequence) |
java.time.YearMonth |
YearMonth.parse(CharSequence) |
java.time.ZonedDateTime |
ZonedDateTime.parse(CharSequence) |
java.time.ZoneId |
ZoneId.of(String) |
java.time.ZoneOffset |
ZoneOffset.of(String) |
java.io.File |
new File(String) |
java.nio.charset.Charset |
Charset.forName(String) |
java.nio.file.Path |
Paths.get(String, String...) |
java.net.URI |
URI.create(String) |
java.net.URL |
new URL(String) |
java.math.BigDecimal |
new BigDecimal(String) |
java.math.BigInteger |
new BigInteger(String) |
java.util.Currency |
Currency.getInstance(String) |
java.util.Locale |
new Locale(String) |
java.util.UUID |
UUID.fromString(String) |
java.lang.Class |
* Les détails seront décrits plus tard |
[^ 3]: Erreur si length ()
de la String
d'origine n'est pas 1
[^ 4]: null
est une erreur
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 {}
}
Résultat d'exécution
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
,
long,
float`, etc ...), ʻint [] []
[[I
,[Ljava.lang.String;
, etc ...)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!!"))
);
}
}
Résultat d'exécution
Dynamic Hoge!!
Dynamic Fuga!!
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- testFactory() [OK]
+-- Hoge [OK]
'-- Fuga [OK]
@ TestFactory
renvoient désormais une collection de DynamicNode Mettre en place
―― Strictement parlant, une "collection" peut être l'une des suivantes:
- java.util.Collection
- java.lang.Iterable
- java.util.Iterator
- java.util.stream.Stream
--ArrayStream
, Jupiter fera close ()
, il est donc sûr d'utiliser Stream
généré par Files.lines ()
.DynamicNode
lui-même est une classe abstraite, il s'agit en fait d'une sous-classe DynamicTest ou [DynamicContainer]( Utilisez l'un des https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicContainer.html)
--Dans l'exemple ci-dessus, [dynamicTest (String, Executable)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicTest.html#dynamicTest(java.lang.String,org) .junit.jupiter.api.function.Executable))) est utilisé pour créer une instance de DynamicTest
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()");
}
}
Résultat d'exécution
beforeEach()
testFactory()
Dynamic Hoge!!
Dynamic Fuga!!
afterEach()
beforeEach()
test()
afterEach()
-- @ BeforeEach
et @ AfterEach
ne sont exécutés qu'avant et après la méthode dans laquelle @ TestFactory
est défini, pas avant et après chaque test dynamique.
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."))
))
);
}
}
Résultat d'exécution
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]
junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
--junit-platform.properties
est placé dans la racine du chemin de classe
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) //★ N'oubliez pas de mettre ça!
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);
}
}
Résultat d'exécution
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
--Par défaut, toutes les méthodes de test sont exécutées séquentiellement dans un seul thread
--Si vous définissez junit.jupiter.execution.parallel.enabled = true
dans le fichier de configuration ( junit-platform.properties
placé dans la racine du chemin de classe), l'exécution parallèle sera activée.
--Cependant, avec ce paramètre seul, la méthode de test ne change pas et est exécutée séquentiellement dans un seul thread.
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);
}
}
Résultat d'exécution
test1()@ForkJoinPool-1-worker-5
test2()@ForkJoinPool-1-worker-7
--Si vous spécifiez junit.jupiter.execution.parallel.mode.default = concurrent
dans le fichier de configuration, le mode d'exécution par défaut sera l'exécution parallèle (avec ʻExecutionMode.CONCURRENT` spécifié).
--Si vous spécifiez junit.jupiter.execution.parallel.mode.default = concurrent
, la plupart des méthodes de test seront exécutées en parallèle.
--Cependant, à titre d'exception, les classes et méthodes de test avec les paramètres suivants ne sont pas exécutées en parallèle même si le mode d'exécution par défaut est modifié.
--Tester la classe avec Lifecycle.PER_CLASS
MethodOrderer
autre que MethodOrderer.Random
Exemple de non exécution en parallèle même si le mode d'exécution par défaut est modifié
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);
}
}
Résultat d'exécution
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
StandardNestedTest
s'exécute dans un thread différent
--Mais les méthodes de test pour PerClassTest
et ʻOrderedTests'exécutent chacune dans le même thread. --Si vous souhaitez exécuter les méthodes de test de ces classes en parallèle [^ 5], assurez-vous que la classe de test est thread-safe, puis spécifiez explicitement
@Execution (ExecutionMode.CONCURRENT)`.[^ 5]: C'est une étrange histoire d'exécuter des tests qui spécifient l'ordre avec @ TestMethodOrder
en parallèle.
Spécifier explicitement l'exécution parallèle
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) {...}
}
Résultat d'exécution
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);
}
}
Résultat d'exécution
BarTest.test1()@ForkJoinPool-1-worker-3
FooTest.test1()@ForkJoinPool-1-worker-7
BarTest.test2()@ForkJoinPool-1-worker-3
FooTest.test2()@ForkJoinPool-1-worker-7
--Si vous spécifiez junit.jupiter.execution.parallel.mode.classes.default = concurrent
dans le fichier de configuration, vous ne pouvez modifier que le mode d'exécution par défaut des classes de niveau supérieur.
--Le junit.jupiter.execution.parallel.mode.default = concurrent
qui a été défini précédemment contrôle l'exécution parallèle au niveau de la méthode, et cette fois il n'est pas défini (d'où la valeur par défaut same_thread
. Même état que spécifié)
FooTest
et BarTest
sont exécutés dans différents threads (ForkJoinPool-1-worker-7
et ForkJoinPool-1-worker-3
)
--Mais chaque méthode de FooTest
est exécutée dans un thread ( ForkJoinPool-1-worker-7
)
--Il existe un total de 4 combinaisons de paramètres pour junit.jupiter.execution.parallel.mode.default
et junit.jupiter.execution.parallel.mode.classes.default
, et chacune fonctionne comme suit.--Si junit.jupiter.execution.parallel.mode.classes.default
(comportement par défaut pour chaque classe) n'est pas spécifié, la même valeur de réglage que junit.jupiter.execution.parallel.mode.default
devenir
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();
}
}
--Classe pour compter le nombre de threads exécutés simultanément
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);
}
}
Résultat d'exécution
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
junit.jupiter.execution.parallel.config.dynamic.factor
dans le fichier de configuration.factor
, donc le nombre de threads exécutés en parallèle est de 16.factor
n'est pas spécifié, la valeur par défaut est 1.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
Résultat d'exécution
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
est spécifié.Résultat d'exécution
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
--Si vous spécifiez fixed
dans junit.jupiter.execution.parallel.config.strategy
, vous pouvez spécifier un nombre fixe de parallèles.
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
ne peut être référencé que lors de l'exécution par défaut, il est donc spécifié dans testImplementation
afin qu'il puisse également être référencé au moment de la compilation.
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;
}
};
}
}
--Créez une classe qui implémente ParallelExecutionConfigurationStrategy
createConfiguration ()
retourne une instance qui implémente ParallelExecutionConfiguration
.
--Le Getter défini dans ParallelExecutionConfiguration
est utilisé lors de la génération de ForkJoinPool
.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. Voir 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
--Spécifiez custom
dans junit.jupiter.execution.parallel.config.strategy
--Spécifiez la classe d'implémentation de votre propre ParallelExecutionConfigurationStrategy
avec junit.jupiter.execution.parallel.config.custom.class
dynamic
.Résultat d'exécution
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
ParallelExecutionConfiguration
renvoyé par MyParallelExecutionConfigurationStrategy
.Lorsqu'il n'y a pas de contrôle exclusif
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);
}
}
Résultat d'exécution
begin test3
begin test1
begin test2
end test1
end test2
end test3
n = 13394
--Bien sûr, le résultat ne sera pas de 30 000 car il n'est pas synchronisé.
Quand le contrôle exclusif est mis
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() { ... }
}
--Définissez l'annotation @ ResourceLock
sur chaque méthode
Résultat d'exécution
begin test1
end test1
begin test2
end test2
begin test3
end test3
n = 30000
valeur
--Synchronisé entre @ ResourceLock
avec la même chaîne de clé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);
}
}
Résultat d'exécution
begin test1(READ_WRITE)
end test1(READ_WRITE)
begin test2(READ)
begin test3(READ)
end test2(READ)
end test3(READ)
mode
of @ ResourceLock
--Il peut être exécuté en parallèle entre les READ
s, mais il est exclusivement contrôlé lorsque READ_WRITE
est impliqué.
--READ
est spécifié pour la méthode de test qui lit uniquement les données et ne se met pas à jour, et READ_WRITE
est spécifié pour la méthode de test qui met à jour les données.
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 {}
Résultat d'exécution
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()
JUnit Jupiter dispose d'un mécanisme appelé ** Extension Model **, qui facilite l'introduction de toute 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()");
}
}
BeforeEachCallback
, la méthode beforeEach ()
qui est rappelée avant que chaque test ne soit défini.
ʻAfterEachCallbackdéfinit une méthode ʻafterEach ()
qui sera rappelée après chaque testJUnit5Test
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()");
}
}
@ ExtendWith
à l'endroit où vous souhaitez appliquer l'extension et spécifiez l'objet Class
de l'extension que vous souhaitez appliquer à valeur
.Résultat d'exécution
MyExtension.beforeEach()
test1()
MyExtension.afterEach()
MyExtension.beforeEach()
test2()
MyExtension.afterEach()
@ ExtendWith
a été spécifié pour la classe, mais l'extension peut être partiellement appliquée en la spécifiant dans la méthode.test1()Lorsque l'extension est appliquée uniquement à la méthode
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()");
}
}
Résultat d'exécution
MyExtension.beforeEach()
test1()
MyExtension.afterEach()
test2()
ʻExtension` Il existe les interfaces suivantes qui sont des points d'extension qui héritent de l'interface.
interface | La description |
---|---|
ExecutionCondition | Contrôle si le test est exécuté. |
TestInstanceFactory | Créez une instance de test. |
TestInstancePostProcessor | Effectuez le traitement d'initialisation après la génération de l'instance de test. |
ParameterResolver | Résolvez les arguments tels que les méthodes de test et les méthodes de cycle de vie. |
BeforeAllCallback BeforeEachCallback BeforeTestExecutionCallback AfterTestExecutionCallback AfterEachCallback AfterAllCallback |
Exécutez le traitement tout au long du cycle de vie, par exemple avant et après l'exécution du test. |
TestWatcher | Exécutez le post-traitement en fonction du résultat de l'exécution de la méthode de test. |
TestExecutionExceptionHandler | Gère les exceptions levées lors de l'exécution du test. |
LifecycleMethodExecutionExceptionHandler | @BeforeEach Gérez les exceptions levées par des méthodes de cycle de vie telles que. |
TestTemplateInvocationContextProvider | Effectue un traitement préparatoire pour exécuter le même test dans différents contextes. |
Les classes utilitaires (classes de support) qui peuvent être utilisées à des fins générales sont fournies lors de l'implémentation des classes d'extension.
Class
La gestion des exceptions et la description gênante sont omises et il est possible d'opérer de manière concise.
Pour le moment, gardez à l'esprit que "ce genre de chose existe", et lorsque vous commencez à créer une classe d'extension, pensez "Oh, pouvez-vous utiliser cette classe de support?" Et cherchez la méthode que vous voulez. Je pense que c'est bien.
Ces classes ne sont pas des utilitaires internes, mais sont fournies comme une aide lorsqu'un tiers crée leur propre TestEngine
ou extension, vous pouvez donc les utiliser en toute confiance.
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.");
}
}
}
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"))
);
}
}
Résultat d'exécution
# 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()
car la valeur peut ne pas exister dans --Si vous savez qu'il ne sera jamais
null, alors [getRequiredTestMethod ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext. Vous pouvez également utiliser une méthode avec
Required`, telle que html # getRequiredTestMethod ()).[^ 6]: La documentation dit conteneur
, mais elle ne dit pas exactement à quoi conteneur
fait référence (probablement une classe de test ou un test dynamique tel que DynamicContainer
". Je pense que cela fait référence au catamari qui résume les méthodes de test)
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() + "]"))
);
}
}
Résultat d'exécution
===========================================
* 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]
createTestInstance ()
est appelée avant l'exécution de chaque méthode de testPER_CLASS
, il ne sera appelé qu'une seule fois pour chaque classe.
--L'instance retournée par createTestInstance ()
est utilisée dans le test@ Nested
, elle sera appelée une fois pour générer la classe externe, puis la méthode sera appelée à nouveau pour générer la classe de test interne.getOuterInstance ()
de TestInstanceFactoryContext
n'est pas vide, vous pouvez donc en juger.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() + "]");
}
}
Résultat d'exécution
testInstance.hash = 756008141
test1() [756008141]
testInstance.hash = 282044315
test2() [282044315]
--Utilisez TestInstancePostProcessor pour obtenir une instance de test avant que la méthode de test ne soit exécutée. Peut être reçu et effectuer n'importe quel traitement
--Il semble être utilisé pour injecter une dépendance dans l'instance de test ou appeler la méthode d'initialisation.
――N'est-ce pas d'accord avec BeforeEachCallback
?
--Si c'est BeforeEachCallback
, l'instance de test sera obtenue à partir de ʻExtensionContext, donc je me demande si c'est ʻOptional
ou getRequiredInstance ()
.
――Après cela, il peut y avoir un avantage que vous pouvez clarifier l'intention de l'implémentation pour "effectuer un post-traitement pour l'instance de test" (spéculation)
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("-----------------------------------------");
}
}
Résultat d'exécution
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)
-----------------------------------------
@ Test
mais aussi les arguments de méthodes telles que @ TestFactory
et @ BeforeEach
peuvent être résolus.
--supportsParameter ()
est appelé pour chaque argument de méthode
-De ParameterContext, vous pouvez vous référer aux méta-informations de l'argument.true
s'il prend en charge la résolution d'argument, sinon il retourne false
--Si supportsParameter ()
renvoie true
, alors` resolverParameter () ʻest appelé.Parameter.getName ()
, il est nécessaire d'ajouter l'option -parameters
lors de la compilation avec javac
., ʻarg1
.
--Si vous construisez avec Gradle, vous pouvez le définir comme 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()");
}
}
Résultat d'exécution
BeforeAllCallback
beforeAll()
BeforeEachCallback
beforeEach()
BeforeTestExecutionCallback
test()
AfterTestExecutionCallback
afterEach()
AfterEachCallback
afterAll()
AfterAllCallback
@ BeforeAll
@ BeforeEach
@ BeforeEach
et avant la méthode de test.@ AfterEach
et après la méthode de test.@ 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");
}
}
Résultat d'exécution
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
testDisabled()
--Exécuter lorsque le test était invalidetestSuccessful()
--Exécuter lorsque le test est réussitestAborted()
--Exécuter lorsque le test est interrompu (comme ʻassumeThat () `)testFailed()
default
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");
}
}
}
--Si vous recevez NullPointerException
, relancez tel quel
--Throw ʻUnsupportedOperationException si ʻIllegalStateException
est reçu
-Autre que ça se termine sans rien faire
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");
}
}
Résultat d'exécution
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
handleTestExecutionException ()
est appelé.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("Si la méthode du cycle de vie lève normalement une exception")
void test() {
System.out.println("InnerClass1");
}
}
@Nested
@ExtendWith(MyTestExecutionExceptionHandler.class)
class InnerClass2 {
@Test
@DisplayName("Fonctionnement de TestExecutionExceptionHandler pour les exceptions levées dans les méthodes de cycle de vie")
void test() {
System.out.println("InnerClass2");
}
}
@Nested
@ExtendWith(MyLifecycleMethodExecutionExceptionHandler.class)
class InnerClass3 {
@Test
@DisplayName("Lorsque l'exception levée par la méthode du cycle de vie est pressée par 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("Si le LifecycleMethodExecutionExceptionHandler ne serre pas l'exception levée par la méthode du cycle de vie")
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>");
}
}
Résultat d'exécution
[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]
| '--Si le LifecycleMethodExecutionExceptionHandler ne serre pas l'exception levée par la méthode du cycle de vie[X] beforeEach@InnerClass4
+-- InnerClass3 [OK]
| '--Lorsque l'exception levée par la méthode du cycle de vie est pressée par LifecycleMethodExecutionExceptionHandler[OK]
+-- InnerClass2 [OK]
| '--Fonctionnement de TestExecutionExceptionHandler pour les exceptions levées dans les méthodes de cycle de vie[X] afterEach@InnerClass2
'-- InnerClass1 [OK]
'--Si la méthode du cycle de vie lève normalement une exception[X] afterEach@InnerClass1
TestExecutionExceptionHandler
dans la section précédente ne peut être géré que lorsqu'une exception se produit dans la méthode de test elle-même.
--TestExecutionExceptionHandler
n'est pas rappelé lorsqu'une exception est levée dans une méthode de cycle de vieLifecycleMethodExecutionExceptionHandler
--Chaque méthode correspond à @ BeforeAll
, @ BeforeEach
, @ AfterEach
, @ AfterAll
, respectivement.TestExecutionExceptionHandler
, vous pouvez presser une exception sans renvoyer l'exception reçue dans l'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()");
}
}
Résultat d'exécution
[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()]
...
supportsTestTemplate ()
of TestTemplateInvocationContextProvider
est appelé pour chaque méthode pour laquelle @ TestTemplate
est défini.
--Mise en œuvre pour renvoyer true
lors du ciblage (prise en charge) du test cible@ TestTemplate
mais qu'aucun des TestTemplateInvocationContextProvider
de prise en charge n'existe, vous obtiendrez une erreur.
--Si pris en charge, provideTestTemplateInvocationContexts ()
sera appelé
--Cette méthode implémente pour renvoyer Stream
de TestTemplateInvocationContext
--TestTemplateInvocationContext
représente un contexte lors de l'exécution d'un test
--Construire Stream
pour renvoyer plusieurs éléments lors de l'exécution dans plusieurs contextesStream
avec 3MyTestTemplateInvocationContext
, donc la méthodetest1 ()
est exécutée 3 fois (dans 3 contextes).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 + "]";
}
}
}
Résultat d'exécution
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- test1() [OK]
| +-- Hoge[1] [OK]
| +-- Fuga[2] [OK]
| '-- Piyo[3] [OK]
:
getDisplayName ()
est définie dans TestTemplateInvocationContext
." ["+ invocationIndex +"] "
.
--ʻInvocationIndex` reçoit l'index du contexte actuel (commençant par 1)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);
}
}
}
Résultat d'exécution
...
beforeEachCallback()
test1()
test1()
afterEachCallback()
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
+-- test1() [OK]
| +-- BeforeEach [OK]
| '-- AfterEach [OK]
:
getAdditionalExtensions ()
est définie dans TestTemplateInvocationContext
.
--Cette méthode retourne l'extension à utiliser dans ce contexte avec List <Extension>
--Cette méthode est également la méthode par défaut, et l'implémentation par défaut renvoie une Liste <Extension>
vide.BeforeEachCallback
est défini dans le premier contexte et ʻAfterEachCallbak` est défini dans le second contexte.ParameterResolver
, vous pouvez passer différents paramètres pour chaque contexte et exécuter la même méthode de test.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()");
}
}
Résultat d'exécution
[classField] beforeAll()
beforeAll()
[classField] beforeEach()
[instanceField] beforeEach()
beforeEach()
test1()
@ ExtendWith
, l'ajustement de la classe d'extension est fondamentalement statique.
--Jupiter crée une instance de la classe qui implémente l'extension en arrière-plan.statique
ou instance) de la classe de test que vous souhaitez utiliser l'extension.@ RegisterExtension
, vous pouvez enregistrer l'instance définie dans ce champ en tant qu'extension.static
BeforeAllCallback
et les extensions au niveau de l'instance telles que TestInstancePostProcessor
ne sont pas disponibles lors de l'utilisation des champs d'instance.
--Ignoré même s'il est implémentéBeforeEachCallback
sont disponibles[^ 7]: Vous pouvez simplement le générer avec un constructeur, préparer un mécanisme comme un constructeur, ou en faire une méthode d'usine.
Structure des dossiers
`-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()");
}
}
Résultat d'exécution
MyServiceLoaderExtension.beforeEach()
test1()
/ META-INF / services /
sous le chemin de classe et créez un fichier nommé ʻorg.junit.jupiter.api.extension.Extension` dedans.
--Dans le fichier, décrivez le nom complet de la classe d'extension à enregistrer.true
pour le paramètre de configuration junit.jupiter.extensions.autodetection.enabled
.junit-platform.properties
), mais il peut également être spécifié dans les propriétés système.Considérez comment faire référence aux informations enregistrées lorsqu'une extension est exécutée lorsqu'une autre extension est exécutée.
Par exemple, une image qui enregistre l'heure de début d'une méthode de test avec BeforeEachCallback
et affiche l'heure d'exécution à partir de la différence entre l'heure actuelle et l'heure de début à ʻAfterEachCallback`.
Si vous créez une extension dans une seule classe, un moyen rapide d'utiliser les variables d'instance vous vient à l'esprit.
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 + ")");
}
}
En fait, appliquez-le au test suivant et exécutez-le.
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);
}
}
Résultat d'exécution
[test1()] time=211 (startTime=1577191073870, endTime=1577191074081)
[test2()] time=400 (startTime=1577191074126, endTime=1577191074526)
[test3()] time=602 (startTime=1577191074529, endTime=1577191075131)
Cela a fonctionné comme ça.
Cependant, il existe ** des problèmes avec cette implémentation **.
Lorsque ce test est exécuté en parallèle, le problème devient 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
Résultat d'exécution
[test1()] time=214 (startTime=1577191442987, endTime=1577191443201)
[test3()] time=363 (startTime=1577191443234, endTime=1577191443597)
[test2()] time=402 (startTime=1577191443234, endTime=1577191443636)
Le temps d'exécution de test3 ()
est d'environ 300 ms (en fait, cette valeur est impossible car il dort pendant 600 ms).
Si vous regardez attentivement, vous pouvez voir que startTime
de test3 () ʻet
test2 ()ont la même valeur. Cela signifie que le
startTime utilisé pour l'heure
test3 ()est devenu l'heure de début de
test2 ()`.
La cause de ce problème est que le «startTime» est partagé par la variable d'instance «MyStopwatch».
MyStopwatch
utilise la même instance pendant l'exécution du test JUnit5Test
.
En d'autres termes, la «MyStopwatch» utilisée lorsque chaque méthode de test est exécutée est la même instance.
Lorsque test3 ()
est exécuté, beforeEach ()
enregistre l'heure de début dans startTime
.
Cependant, immédiatement après cela, test2 ()
a été exécuté en parallèle, et la valeur de startTime
a été écrasée par l'heure de début detest2 ()
.
En conséquence, les problèmes mentionnés ci-dessus se sont produits.
De cette manière, l'utilisation de variables d'instance de classe d'implémentation pour partager des données entre des extensions peut entraîner des problèmes inattendus selon la manière dont les tests sont exécutés. (Il peut y avoir d'autres modèles qui posent des problèmes, mais pour le moment, le seul cas auquel je puisse penser est ce cas d'exécution parallèle.)
Je ne sais pas si cela est destiné à résoudre ce problème, mais Store
vous permet d'implémenter le partage de données afin qu'il ne pose pas de problèmes lorsqu'il est exécuté en parallèle.
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 + ")");
}
}
Résultat d'exécution
[test1()] time=213 (startTime=1577193397891, endTime=1577193398104)
[test3()] time=609 (startTime=1577193397891, endTime=1577193398500)
[test2()] time=401 (startTime=1577193398142, endTime=1577193398543)
Le temps de test3 ()
est d'environ 600 ms et cela fonctionne bien.
(StartTime
est le même que test1 ()
car test1 () ʻet
test3 () ʻont démarré en même temps avec 2 parallèles, donc il n'y a pas de problème)
Cette implémentation utilise un mécanisme appelé Store.
Store
est un conteneur de données préparé pour chaque ʻExtensionContext`, et toutes les données peuvent être enregistrées au format clé-valeur.
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
store.put("startTime", System.currentTimeMillis());
...
long startTime = store.get("startTime", long.class);
Une instance de Store
est [getStore (Namespace)] de ʻExtensionContext` (https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html#getStore(org) Il peut être obtenu par la méthode .junit.jupiter.api.extension.ExtensionContext.Namespace)).
Spécifiez Namespace comme argument.
En tant qu'image, plusieurs Store
s sont stockés dans ʻExtensionContext, et il semble que
Namespace spécifie quel
Storeobtenir. (En fait,
Namespace n'est utilisé que dans le cadre de la clé, et le
Storeréel est implémenté par une seule
Map`, mais je pense que c'est bien comme une image.)
En séparant «Store» par «Namespace» de cette manière, même s'il y a plusieurs extensions qui utilisent la même clé, «Store» peut être séparé et les données peuvent être partagées. Au fait, si vous souhaitez partager des données avec toutes les extensions, [Namespace.GLOBAL](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace Vous pouvez également utiliser une constante prédéfinie appelée .html # GLOBAL).
[Create (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace.html#create pour créer Namespace
(java.lang.Object ...)) Utilisez la méthode.
Tout objet peut être spécifié comme argument, mais ce doit être un objet qui peut être comparé et vérifié avec la méthode ʻequals () `.
Le cycle de vie du Store correspond à la 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());
}
}
--Dans beforeAll ()
et beforeEach ()
, affichez d'abord les informations de Store
, puis définissez la valeur de hoge
.
--Et, ʻafterEach () ʻet ʻafterAll () sortent les informations de
Store telles quelles. --Chaque, le nom de classe de ʻExtensionContext
est également affiché.
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 {}
}
Résultat d'exécution
[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
--Dans les extensions de niveau classe comme BeforeAllCallback
et ʻAfterAllCallback,
ClassExtensionContext est passé en tant que ʻExtensionContext
.
BeforeEachCallback
et ʻAfterEachCallback passent
MethodExtensionContext`. reçoit une instance d'implémentation qui correspond au niveau auquel l'extension est en cours d'exécution. --Ces ʻExtensionContext
ont une relation parent-enfant, et le parent de MethodExtensionContext
est ClassExtensionContext
.
--Si vous suivez le contexte du parent, vous vous retrouverez avec JupiterEngineExtensionContext
. (https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getParent- -) Peut être obtenu par méthode --
JupiterEngineExtensionContext` est le contexte du plus parent (racine)TestTemplateExtensionContext
et DynamicExtensionContext
dans la classe d'implémentation de ʻExtensionContext, mais ils sont omis ici. --
Store est conservé pour chaque instance de ces contextes --
MethodExtensionContext` est généré pour chaque méthode de testMethodExtensionContext
de test1 () ʻet
test2 () ʻest passé à différentes instances.
«Par conséquent,« Store »est également différent.Store
par test1 ()
ne peuvent pas être référencées partest2 ()
.MyStopwatch
a pu éviter les problèmes lors de l'exécution parallèle en utilisant Store
.
--BeforeEachCallback
et ʻAfterEachCallback` sont des extensions au niveau de la méthodeStore
est également différent.
――Pour cette raison, même s'ils sont exécutés en parallèle, il n'y a pas de conflit de données et ils peuvent fonctionner normalement.Store
peuvent également être référencées à partir du contexte enfant Store
.
-Et il peut être écrasé dans le contexte enfant
--Cependant, les informations écrasées reviennent à la valeur d'origine définie dans le contexte parent lorsque la portée du contexte enfant se termine et se déplace vers un autre contexte.
--La valeur définie dans test1 ()
retourne à l'original " VALEUR INITIALE "
au stade de beforeEach () ʻet
afterAll () ʻoftest2 ()
.
--Ce comportement est obtenu en recherchant récursivement le parent Store
si les informations pour la clé spécifiée n'existent pas dans Store
.Store
de MethodExtensionContext
, il recherchera Store
du parent ClassExtensionContext
, s'il n'est pas là, il sera recherché en traçant vers le Store
de la racine.
--Cela permet de faire référence à la valeur définie dans le contexte parent de test1 ()
.
--Cependant, même si vous définissez une valeur sur Store
dans le contexte enfant, le Store
dans le contexte parent reste le même, donc lorsque le contexte enfant se termine, les informations dans le contexte parent sont restaurées.
--Si vous dessinez la relation entre ʻExtensionContext et
Store` dans un diagramme, elle ressemble à celle ci-dessous.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()");
}
}
Résultat d'exécution
test1()
Close Resource > BeforeEach(test1())
test2()
Close Resource > BeforeEach(test2())
afterAll()
Close Resource > BeforeAll
--Une instance qui implémente CloseableResource est enregistrée dans Store
. Si tel est le cas, à la fin du cycle de vie du Store
[close ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext. La méthode Store.CloseableResource.html # close ()) est appelée automatiquement
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);
}
}
Résultat d'exécution
[test1] testMethodName=test1
[test2] testMethodName=test2
--JUnit Jupiter prend en charge le mécanisme de méta-annotation
Class
avec @ ExtendWith
pour la fonction d'extension, il peut y avoir un avantage qu'il peut être séparé de l'implémentation concrète en insérant une annotation faite soi-même.Pour ajuster le comportement de test tel que junit.jupiter.testinstance.lifecycle.default
pour changer le cycle de vie par défaut d'une instance de test et junit.jupiter.execution.parallel.enabled
pour activer l'exécution parallèle. Les paramètres sont appelés ** paramètres de configuration **.
Les trois méthodes suivantes sont disponibles pour spécifier les paramètres de réglage.
junit-platform.properties
La première méthode fournie pour chaque lanceur est, par exemple, lorsque vous utilisez ConsoleLauncher, spécifiez-le avec l'option --config
.
Gradle ne semble pas actuellement prendre en charge la spécification de cette façon (à la place, il utilise les propriétés système ou junit-platform.properties
).
Pour Maven, spécifiez avec la propriété configurationParameters
.
La deuxième propriété système JVM est spécifiée telle qu'elle est dans la propriété système (la méthode de spécification varie en fonction de chaque lanceur).
Le troisième est activé en plaçant un fichier de propriétés appelé junit-platform.properties
à la racine du chemin de classe.
Si les mêmes paramètres de configuration de clé sont spécifiés de ces différentes manières, la valeur définie ci-dessus est prioritaire. Autrement dit, la valeur fournie pour chaque lanceur a la priorité la plus élevée et la valeur définie dans «junit-platform.properties» a la priorité la plus basse.
En fait, essayez-le avec 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>"));
}
}
--La valeur du paramètre de réglage peut être référencée par getConfigurationParameter ()
de ʻExtensionContext`.
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
définit tout
> java -Dfuga=FUGA@SystemProperty ^
-Dpiyo=PIYO@SystemProperty ^
-jar junit-platform-console-standalone-1.5.2.jar ^
--config piyo=PIYO@ConfigOption ^
...
--fuga
et piyo
sont spécifiés dans les propriétés système JVM
--Seul piyo
est également spécifié dans l'option --config
.
Résultat d'exécution
key=hoge, value=HOGE@properties
key=fuga, value=FUGA@SystemProperty
key=piyo, value=PIYO@ConfigOption
test1()
Le tableau ci-dessous montre la relation entre chaque clé et la valeur spécifiée pour chaque méthode de réglage. (La valeur avec «*» est la valeur finalement adoptée)
Clé | junit-platform.properties | Propriétés du système JVM | --config |
---|---|---|---|
hoge |
HOGE@properties * |
||
fuga |
FUGA@properties |
FUGA@SystemProperty * |
|
piyo |
PIYO@properties |
PIYO@SystemProperty |
PIYO@ConfigOption * |
On peut voir que la valeur spécifiée par --config
a la priorité et la valeur spécifiée par junit-platform.properties
a une priorité inférieure.
JUnit5 fournit une classe d'assertions appelée Assertions.
Ici, assertEquals () sont fournies. Aussi, [assertTimeout ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertTimeout(java.time.Duration,org.junit.jupiter. api.function.Executable)) et [assertAll ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertAll(org.junit.jupiter) Il existe également des méthodes utiles comme .api.function.Executable ...)).
Cependant, l'impression est que seuls les éléments minimaux nécessaires sont préparés.
JUnit5 vous permet d'utiliser n'importe quelle bibliothèque d'assertions tierce. En d'autres termes, si vous ajoutez AssertJ ou Hamcrest aux bibliothèques dépendantes, vous pouvez utiliser JUnit4. Peut être utilisé normalement.
Voici un exemple d'ajout d'AssertJ à une dépendance.
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);
}
}
Résultat d'exécution
...
.
'-- JUnit Jupiter [OK]
'-- JUnit5Test [OK]
'-- test() [X]
Expecting:
<10>
to be equal to:
<8>
but was not.
...
En fait, je pense que j'utiliserai ces affirmations de tiers, donc j'omettrai comment utiliser les assertions standard.
Recommended Posts