[JAVA] Notes d'utilisation de JUnit5

Qu'est-ce que JUnit5

Inutile de dire que la dernière version majeure du framework de test Java à partir de 2019.

environnement

> 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

la mise en oeuvre

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

Exécuter dans ConsoleLauncher

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.

Critères de recherche de classe de test

> 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?)$]
...

Courir depuis Gradle

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

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.

Classes d'intérêt lorsqu'elles sont exécutées dans Eclipse

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 ".

junit5.jpg

«Hoge» et «StaticClass» ont également été ciblés.

Classes d'intérêt lorsqu'elles sont exécutées dans IntelliJ IDEA

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.jpg

architecture

junit5.jpg

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.

en bref

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.

Artefacts requis

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 **

junit5.jpg

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 **

junit5.jpg

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.

Comment rédiger un test

Méthode d'essai

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.

Pré-traitement / post-traitement

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é

Afficher un nom

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 **

junit5.jpg

junit5.jpg

** 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: &lt;10&gt; but was: &lt;8&gt;" type="org.opentest4j.AssertionFailedError">org.opentest4j.AssertionFailedError: expected: &lt;10&gt; but was: &lt;8&gt;
...

Test imbriqué

package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

class JUnit5Test {
    
    @BeforeEach
    void beforeEach() {
        System.out.println("JUnit5Test.beforeEach()");
    }

    @Test
    void test1() {
        System.out.println("  JUnit5Test.test1()");
    }

    @Test
    void test2() {
        System.out.println("  JUnit5Test.test2()");
    }

    @AfterEach
    void afterEach() {
        System.out.println("JUnit5Test.afterEach()");
    }
    
    @Nested
    class NestedTest {

        @BeforeEach
        void beforeEach() {
            System.out.println("  NestedTest.beforeEach()");
        }

        @Test
        void test1() {
            System.out.println("    NestedTest.test1()");
        }

        @Test
        void test2() {
            System.out.println("    NestedTest.test2()");
        }

        @AfterEach
        void afterEach() {
            System.out.println("  NestedTest.afterEach()");
        }
    }
}

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.

Spécifiez les prérequis

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.

Désactiver le test

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)

Test conditionnel

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.

Version Java

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.

Propriétés du système

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)

Variable d'environnement

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)

Filtrage des tags

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)

junit5.jpg

-Vous pouvez baliser les cas de test en attachant @Tag aux classes et méthodes de test.

build.gradle


...
test {
    useJUnitPlatform {
        includeTags "foo | !fizz"
    }
}

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)

Ordre d'exécution des tests

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.

Ordre alphabétique

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.

Spécifié par l'annotation de commande

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

Aléatoire

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.

Cycle de vie de l'instance de test

Cycle de vie par défaut

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.

Changer le cycle de vie de chaque classe de test

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

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.

Changer le cycle de vie par défaut

--Par défaut, une instance de test est créée pour chaque méthode de test

Test répété

package 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) **

junit5.jpg

-Lorsque vous annotez une méthode avec @RepeatedTest, le test sera effectué le nombre de fois spécifié par value. Sera répété

Spécifiez le nom d'affichage

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) **

junit5.jpg

Recevez des informations répétitives avec la méthode de test

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)

Test paramétré

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) **

junit5.jpg

-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 **.

Énumération 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

N'utilisez que certaines constantes

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

Exclure certaines constantes

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

Spécifiez avec une expression régulière

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

Méthode source

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource
    void test(String value) {
        System.out.println("value=" + value);
    }

    static List<String> test() {
        return List.of("hoge", "fuga", "piyo");
    }
}

Résultat d'exécution


value=hoge
value=fuga
value=piyo

Spécifiez la méthode source

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

Méthodes source d'autres classes

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`.

Passer plusieurs arguments formels avec un paramètre

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

Texte CSV source

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".

Fichier CSV source

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

Créer une classe source réutilisable

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`

Conversion de paramètres

Conversion d'agrandissement

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

Conversion implicite

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
Types et méthodes de conversion prenant en charge la conversion de type implicite
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)
toutenumType (java.lang.EnumSous-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

Conversion de java.lang.Class
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

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.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]

Cycle de vie de test dynamique

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.

Imbrication de tests dynamiques

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]

Exécution parallèle

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.

Changer le mode d'exécution par défaut

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é).

Exceptions auxquelles le mode d'exécution par défaut ne s'applique pas

--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

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

[^ 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

Modifier le mode d'exécution par défaut pour les classes de niveau supérieur

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é)

junit5.jpg

--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

Ajuster le nombre de parallèles simultanés

Changement dynamique en fonction du nombre de processeurs (cœurs)

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-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

Définir sur une valeur fixe

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

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.

Personnalisez comme vous le souhaitez

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

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

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

contrôle d'exclusion

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

Spécifiez le mode d'accès

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)

junit5.jpg

--Il peut être exécuté en parallèle entre les READs, 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.

Utilisez la méthode par défaut de l'interface

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()

Modèle étendu

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()");
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MyExtension.class)
class JUnit5Test {

    @Test
    void test1() {
        System.out.println("  test1()");
    }

    @Test
    void test2() {
        System.out.println("  test2()");
    }
}

Résultat d'exécution


MyExtension.beforeEach()
  test1()
MyExtension.afterEach()
MyExtension.beforeEach()
  test2()
MyExtension.afterEach()

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()

Point d'expansion

ʻ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 @BeforeEachGé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.

Classe de soutien

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.

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.

Contrôler les conditions d'exécution des tests

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()

[^ 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)

Créer une instance 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]

Effectuer le traitement d'initialisation après la génération de l'instance de test

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)

Résoudre les paramètres

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)
-----------------------------------------

Effectuer le traitement tout au long du cycle de vie

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

Effectuer le traitement en fonction du résultat du test

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>

Gérer les exceptions levées dans le test

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

Gérer les exceptions levées par les méthodes du cycle de vie

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

Exécutez le même test dans différents contextes

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()]
       ...

Spécifiez le nom d'affichage dans le contexte

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]
    :

Ajouter des extensions arbitraires pour chaque contexte

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]
    :

Enregistrer l'extension de manière procédurale

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()

[^ 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.

Inscrivez-vous automatiquement à l'aide de ServiceLoader

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()

Partager des données entre les extensions

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 lestartTime utilisé pour l'heure test3 ()est devenu l'heure de début detest2 ()`.

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.)

Utiliser le magasin

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 Stores 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 seuleMap`, mais je pense que c'est bien comme une image.)

junit5.jpg

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 () `.

Comment trouver le cycle de vie et les clés du magasin

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.

junit5.jpg

Effectuer le traitement à la fin du cycle de vie

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

Méta-annotation

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

Paramètres de réglage

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.

  1. Spécifiez par la méthode préparée pour chaque lanceur
  2. Spécifié dans les propriétés du système JVM
  3. Spécifiez dans 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.

Affirmation

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.

référence

Recommended Posts

Notes d'utilisation de JUnit5
Notes d'utilisation de JavaParser
Notes d'utilisation de WatchService
Notes d'utilisation de Spring Shell
Mémo d'utilisation de Spring Security CSRF
Mémo d'utilisation de Spring Security Run-As
Introduction à JUnit (note d'étude)
Spring Security Usage Memo Method Security
Mémo d'utilisation de Spring Security Remember-Me
Notes sur l'utilisation du plug-in de gestion des dépendances
Junit
Test de mémo d'utilisation de Spring Security
Authentification / autorisation de mémo d'utilisation de Spring Security
Mémo d'utilisation de JCA (Java Encryption Architecture)
En-tête de réponse de mémo d'utilisation de Spring Security
Gestion des sessions de mémo d'utilisation de Spring Security
Mémo d'utilisation de Spring Security Basic / mécanisme
Mémo entier
mémo docker
Spring Security Usage Memo Domain Object Security (ACL)
JUnit 4 notes
Mémo de Lombok
Histoire de JUnit
Mémo Dockerfile
Mémo Java
Mémo AWS
[Personnel] Mémorandum JUnit5 (travail en cours)
Mémorandum JUnit
utilisation d'irb
Memo Stream