JUnit5 使い方メモ

# JUnit5 とは
言わずと知れた Java のテスティングフレームワークの、2019年現在最新のメジャーバージョン。

# 環境
```console
> 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
## 実装
```gradle: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"
}
```

- JUnit5 をとりあえず使い始めるなら、依存関係に `org.junit.jupiter:junit-jupiter` を指定する（<a href="#必要なアーティファクト">詳細後述</a>）

```text:ファイル構成
|-build.gradle
`-src/test/java/
  `-sample/junit5/
    |-JUnit5Test.java
    |-JUnit5Tests.java
    |-TestJUnit5.java
    `-Hoge.java
```

- ４種類のテストクラスを用意している

```java: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);
        }
    }
}
```

- 各クラスは、必ず失敗する `fail()` メソッドだけを定義している
- 静的な入れ子クラスとして `StaticClass` と `StaticTest` の２つを、  
  内部クラスとして `InnerTest` クラスを定義している
- 上の例は `JUnit5Test.java` だが、残りの３つ（`JUnit5Tests.java`, `TestJUnit5.java`, `Hoge.java`）も中身の実装は全部同じにしている


## ConsoleLauncher で実行する
コマンドラインで JUnit5 を実行するためのツールとして、 **ConsoleLauncher** というものが用意されている。
実体は JUnit5 の各モジュールが１つに固められた jar ファイルで、[Maven のセントラルリポジトリ](https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/)からダウンロードしてくる。

ここでは `junit-platform-console-standalone-1.5.2.jar` をダウンロードして検証してみる。

```console
# コンパイルして
> gradle compileTestJava

# 実行
> 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          ]
```

- `-cp` オプションでテストで使用する依存関係（JUnit5 以外）をクラスパスに追加する。
    - JUnit5 のクラス達は `junit-platform-console-standalone-1.5.2.jar` に入っている
- `--scan-classpath` で、実行したいテストクラスを検索する場所を指定する
- テストの結果はコンソールに出力される

### テストクラスの検索条件
```console
> java -jar junit-platform-console-standalone-1.5.2.jar --help
...
  -n, --include-classname=PATTERN
                             Provide a regular expression to include only classes whose fully
                               qualified names match. To avoid loading classes unnecessarily,
                               the default pattern only includes class names that begin with
                               "Test" or end with "Test" or "Tests". When this option is
                               repeated, all patterns will be combined using OR semantics.
                               Default: [^(Test.*|.+[.$]Test.*|.*Tests?)$]
...
```

- 上の例で実行されたテストクラスは以下の通り
    - `Hoge$StaticTest`
    - `JUnit5Test`
    - `JUnit5Test$StaticTest`
    - `JUnit5Tests`
    - `JUnit5Tests$StaticTest`
    - `TestJUnit5`
    - `TestJUnit5$StaticClass`
    - `TestJUnit5$StaticTest`
- 逆に、次のクラスのテストは実行されていない
    - `Hoge`
    - `Hoge$InnerTest`
    - `Hoge$StaticClass`
    - `JUnit5Test$InnerTest`
    - `JUnit5Test$StaticClass`
    - `JUnit5Tests$InnerTest`
    - `JUnit5Tests$StaticClass`
    - `TestJUnit5$InnerTest`
- テストクラスの検索条件は `-n` または `--include-classname` オプションで指定する（正規表現指定）
- デフォルトは `^(Test.*|.+[.$]Test.*|.*Tests?)$`
    - [REGEXPER で確認した結果](https://regexper.com/#%5E%28Test.*%7C.%2B%5B.%24%5DTest.*%7C.*Tests%3F%29%24)
- したがって、 `Test` ではじまるか、 `Test` または `Tests` で終わるクラスが対象になる（静的な入れ子クラスも含む）
    - 内部クラスは対象外（<a href="#ネストしたテスト">後述</a>の `@Nested` が必要）
- テストクラスは `public` でなくてもいい


## Gradle から実行する
でも、普通は使用しているビルドツールから実行すると思う。
Gradle は 4.6 から JUnit5 の実行をネイティブサポートしているので、 Gradle から実行してみる。

```gradle:build.gradle
...

test {
    useJUnitPlatform() // ★追加
}
```

- Gradle は JUni4 や TestNG など他のテスティングフレームワークもサポートしている
- JUnit5 を使う場合は、 JUnit5 を使うということを明示的に宣言する必要がある
- そのための設定が、 `test { useJUnitPlatform() }` になる

```console:テストを実行
> 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
```

- 実行されたのは以下のクラスたち
    - `Hoge` *
    - `Hoge$StaticClass` *
    - `Hoge$StaticTest`
    - `JUnit5Test`
    - `JUnit5Test$StaticClass` *
    - `JUnit5Test$StaticTest`
    - `JUnit5Tests`
    - `JUnit5Tests$StaticClass` *
    - `JUnit5Tests$StaticTest`
    - `TestJUnit5`
    - `TestJUnit5$StaticClass`
    - `TestJUnit5$StaticTest`
- ConsoleLauncher で実行した場合よりも実行されたクラスが増えている（* がついているのが増えたクラス）
- Gradle からテストを実行した場合、デフォルトは [Test タスクの testClassesDirs プロパティ](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testClassesDirs) で指定された場所に存在するクラスが全て対象になる
    - ドキュメントにも書かれている通り、デフォルト値は Java プラグインによって `sourceSets.test` の出力先ディレクトリが設定されている
    - つまり、 `src/test/java` の下にあるクラスたちが全て対象になる（`Test` で始まっているかどうかなどは関係ない）
    - 対象を絞り込みたい場合は、 [includes](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:includes) や [excludes](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:excludes) を指定する
- ただし、内部クラス（`InnerTest`）がデフォルトで対象外なのは ConsoleLauncher と同じ

## Eclipse で実行した場合に対象となるクラス
確認したバージョンは [Pleiades](http://mergedoc.osdn.jp/) の `2019-09 Standard Edition`。

実行方法は、 `src/test/java` フォルダを右クリックして「実行」→「JUnitテスト」。

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/61c13efc-86d7-e1e2-81bb-12a5235c0444.jpeg)

`Hoge` や `StaticClass` も対象になった。

## IntelliJ IDEA で実行した場合に対象となるクラス
確認したバージョンは Community 版の 2019.2.3。

IDEA の場合、 Gradle のプロジェクトとして Open していると Gradle の `test` タスクでテストが実行される。
なので、その場合の動作は Gradle から実行した場合と同じになる。

Gradle を使わずに [Run/Debug Configurations] から実行の構成を指定することもできる。
ただ、その場合は「Test kind」の指定次第となる。

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/b8ef6738-59d9-2c50-9d7b-84b73f5d4bb7.jpeg)



# アーキテクチャ
![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/2c149b75-645e-a180-8752-2046c91972bc.jpeg)

JUnit5 は、大きく３つのモジュール（サブプロジェクト）から成る。

**JUnit Platform**

JVM 上でテストフレームワークを実行するための基盤。
[TestEngine](https://junit.org/junit5/docs/current/api/org/junit/platform/engine/TestEngine.html) というインターフェースを実装したモジュールを実行する仕組みを提供する。

コンソールから起動するための `ConsoleLauncher` なども提供している。

**JUnit Jupiter**

JUnit 5 のテストを作成・実行するためのモジュール。
JUnit 5 向けに `TestEngine` を実装した [JupiterTestEngine](https://junit.org/junit5/docs/current/api/org/junit/jupiter/engine/JupiterTestEngine.html) を提供する。

Jupiter といえば JUnit 5 のことを指していると思えばいいと思う。

**JUnit Vintage**

JUnit Platform 上で JUnit 3, 4 を動かすために `TestEngine` を実装したクラス ― [VintageTestEngine](https://junit.org/junit5/docs/current/api/org/junit/vintage/engine/VintageTestEngine.html) を提供するモジュール。

多分、移行期の互換用に用意されたものだと思うので、新しく JUnit 5 を導入するような場合は不要。


## 要するに
JUnit 5 は、大きく次の２つのモジュールから成る。

- テストフレームワークを実行するための基盤モジュール（Platform）
- 具体的なテストフレームワークのモジュール（Jupiter, Vintage）

Platform はテストを実行するために必要となる。
Jupiter は JUnit 5 でテストを書きたいときに必要で、 Vintage は JUnit 4 を JUnit Platform 上で動かしたいときに必要となる。

## 必要なアーティファクト
Platform や Jupiter というモジュールは、 Maven の分類でいうところの Group に対応している。

- [org.junit.platform](https://mvnrepository.com/artifact/org.junit.platform)
- [org.junit.jupiter](https://mvnrepository.com/artifact/org.junit.jupiter)
- [org.junit.vintage](https://mvnrepository.com/artifact/org.junit.vintage)

それぞれの Group の中には、さらにいくつもの Artifact が存在する。

**各モジュール内の Artifact の依存関係**

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/047091d1-3588-fa31-52d9-fcc73c4c9530.jpeg)

実際に JUnit 5 のテストを書くときは、これらの Artifact から必要なものだけを適切に選択して依存関係に追加する必要がある。
上図からも分かるように、どれが必要かを判断することは、初見では難しい。

そこで、 5.4.0 で `org.junit.jupiter` Group に、 [junit-jupiter](https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter) という Artifact が追加された。

**junit-jupiter が追加された図**

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/e5d6ff8b-68e7-0bc4-4055-87d07cb5f4b5.jpeg)

`junit-jupiter` は、 JUnit 5 でテストを書くために最低限必要な依存関係だけをまとめた Artifact となっている。
したがって、 JUnit 5 でテストを書くだけなら、この Artifact を１つだけ依存関係に追加すれば良いようになっている。

# テストの書き方
## テストメソッド
```java
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);
    }
}
```

- [@Test](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Test.html) で注釈されたメソッドがテストメソッドとなる
- JUnit4 までの [org.junit.Test](https://junit.org/junit4/javadoc/4.12/org/junit/Test.html) とはパッケージが異なる
    - 4 -> 5 への移行期間中に 4, 5 両方のテストを混在させられるようにするためらしい

## 前処理・後処理
```java
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()");
    }
}
```

```text:実行結果
JUnit5Test#beforeAll()
  JUnit5Test#beforeEach()
    JUnit5Test#test1()
  JUnit5Test#afterEach()
  JUnit5Test#beforeEach()
    JUnit5Test#test2()
  JUnit5Test#afterEach()
JUnit5Test#afterAll()
```

- [@BeforeAll](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/BeforeAll.html) をつけたメソッドは、テストクラス内で一番最初に一度だけ実行される
    - メソッドが `static` である必要がある
- [@BeforeEach](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/BeforeEach.html) をつけたメソッドは、各テストメソッドの前に実行される
- [@AfterAll](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/AfterAll.html) をつけたメソッドは、テストクラス内で一番最後に一度だけ実行される
    - メソッドが `static` である必要がある
- [@AfterEach](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/AfterEach.html) をつけたメソッドは、各テストメソッドの後に実行される


## 表示名
```java
package sample.junit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

@DisplayName("クラスやで")
class JUnit5Test {
    @Test
    @DisplayName("成功するで")
    void success() {
        Assertions.assertEquals(10, 10);
    }

    @Test
    @DisplayName("失敗するで")
    void fail() {
        Assertions.assertEquals(10, 8);
    }
}
```

**ConsoleLauncher で実行した場合**

```console
> java -jar junit-platform-console-standalone-1.5.2.jar ^
       -cp build\classes\java\test ^
       --scan-classpath build\classes\java\test

...

.
+-- JUnit Jupiter [OK]
| '-- クラスやで [OK]
|   +-- 成功するで [OK]
|   '-- 失敗するで [X] expected: <10> but was: <8>
'-- JUnit Vintage [OK]

Failures (1):
  JUnit Jupiter:クラスやで:失敗するで
    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          ]
```

**Gradle で実行した場合**

```console
> gradle test
...
> Task :test FAILED

sample.junit5.JUnit5Test > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Test.java:18

2 tests completed, 1 failed

FAILURE: Build failed with an exception.

...

BUILD FAILED in 6s
2 actionable tasks: 1 executed, 1 up-to-date
```

**HTML レポート**

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/fc3f64a4-419d-4233-0ec7-3acfa46a93f6.jpeg)

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/76d589a3-5a9e-d19c-054c-9f92bf53de1a.jpeg)

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

- `@DisplayName` をクラスやメソッドにつけることで、テストの名前を任意の文字列で指定できるようになる
- ただし、 Gradle のレポートは対応がマチマチっぽい

## ネストしたテスト
```java
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()");
        }
    }
}
```

```text:実行結果
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()
```

- 非 `static` な内部クラスを [@Nested](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Nested.html) でアノテートすると、テストクラスを入れ子にできる
    - 非 `static` である必要があるので、必然的に `@BeforeAll`, `@AfterAll` はそのままでは指定できない[^2]
    - どうしても指定したい場合は、後述の<a href="#テストインスタンスのライフサイクル">テストインスンタンスのライフサイクル</a>の指定で `PER_CLASS` を設定する必要がある

[^2]: `@BeforeAll`, `@AfterAll` は `static` なメソッドに指定しなければならないが、非 `static` な内部クラスでは Java 言語仕様上 `static` なメソッドを定義できない


## 前提条件を指定する
```java
package sample.junit5;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class JUnit5Test {

    @Test
    void test1() {
        Assumptions.assumeTrue(true);

        System.out.println("test1()");
    }

    @Test
    void test2() {
        Assumptions.assumeTrue(false);

        System.out.println("test2()");
    }

    @Test
    void test3() {
        Assumptions.assumingThat(true, () -> {
            System.out.println("test3() assumption.");
        });

        System.out.println("test3()");
    }

    @Test
    void test4() {
        Assumptions.assumingThat(false, () -> {
            System.out.println("test4() assumption.");
        });

        System.out.println("test4()");
    }
}
```

```text:実行結果
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          ]
```

- [Assumptions.assumeTrue(boolean)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assumptions.html#assumeTrue(boolean)) を使用すると、引数に `true` を渡したときにだけ続きのテストが実行される
    - `false` だった場合は、そのテストメソッド内の残りの処理は中断される
    - 中断されたテストは successful でも failed でもなく、 aborted という状態となる
- [Assumptions.assumingThat(boolean, Executable)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assumptions.html#assumingThat(boolean,org.junit.jupiter.api.function.Executable)) を使用すると、第一引数の値が `true` だったときだけ、第二引数で渡した処理が実行される

## テストの無効化
```java
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()");
    }
}
```

```text:実行結果
test1()
```

- [@Disabled](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Disabled.html) をつけたテストメソッドは実行されなくなる
- クラスにつけることも可能（その場合は、テストクラス内の全テストメソッドが実行されなくなる）

## 条件付きテスト
### OS
```java
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");
    }
}
```

```text:実行結果
enabled on windows
disabled on mac
```

※実行環境は Windows

- [@EnabledOnOs](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/EnabledOnOs.html) をつけると、特定の OS でだけテストを有効にできる
- [@DisabledOnOs](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/DisabledOnOs.html) をつけると、特定の OS でだけテストを無効にできる
- `value` には [OS](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/OS.html) で定義された定数を指定する


### Java バージョン
```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");
    }
}
```

```text:実行結果
enabled on java 11
disabled on java 12
```

※実行環境は Java 11

- [@EnabledOnJre](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/EnabledOnJre.html) をつけると、特定の Java バージョンでだけテストを有効にできる
- [@DisabledOnJre](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/DisabledOnJre.html) をつけると、特定の Java バージョンでだけテストを無効にできる
- `value` には [JRE](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/JRE.html) で定義された定数を指定する

### システムプロパティ
```java
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");
    }
}
```

```text:実行結果
enabled if AdoptOpenJDK
disabled if Oracle
```

- [@EnabledIfSystemProperty](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html) をつけると、システムプロパティの値を条件にしてテストを有効にできる
- [@DisabledIfSystemProperty](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html) をつけると、システムプロパティの値を条件にしてテストを無効にできる
- `named` に、条件にしたいシステムプロパティの名前を指定する
- `matches` に、条件となる値を正規表現で指定する（全体一致）

### 環境変数
```java
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");
    }
}
```

```text:実行結果
enabled if AdoptOpenJDK
disabled if OpenJDK
```

- [@EnabledIfEnvironmentVariable](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html) をつけると、環境変数の値を条件にしてテストを有効にできる
- [@DisabledIfEnvironmentVariable](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html) をつけると、環境変数の値を条件にしてテストを無効にできる
- `named` に、条件にしたい環境変数の名前を指定する
- `matches` に、条件となる値を正規表現で指定する（全体一致）

## タグ・フィルタリング
```java
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)");
    }
}
```

**普通に実行した場合**

```console:実行結果

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

**--include-tag で対象を絞り込んだ場合**

```console:実行結果
> 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/40ca14f8-bb07-c09b-7f86-16c0da49704e.jpeg)


- [@Tag](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Tag.html) をテストクラスやメソッドにつけることで、テストケースをタグ付けできる
- タグ名に指定する文字列は、次の条件を満たしている必要がある
    - `null` または空文字ではない
    - 空白文字を含まない
    - 制御文字を含まない
    - 以下の予約済みの文字を含まない
        - `(`
        - `)`
        - `,`
        - `&`
        - `|`
        - `!`
- タグは、実行時に指定する条件でフィルタリングできる
- ConsoleLauncher の場合は、
    - `--include-tag` で、条件に一致するタグだけを対象にできる
    - `--exclude-tag` で、条件に一致しないタグだけを対象にできる
- Gradle から実行する場合は次のような感じで指定する

```gradle:build.gradle
...
test {
    useJUnitPlatform {
        includeTags "foo | !fizz"
    }
}
```

- [includeTags](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/junitplatform/JUnitPlatformOptions.html#includeTags-java.lang.String...-) または [excludeTags](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/junitplatform/JUnitPlatformOptions.html#excludeTags-java.lang.String...-) で指定できる
- タグを絞り込む条件は、専用の**タグ式**で記述できる
- タグ式では演算子を使って複雑な条件を記述できる
    - `!`：NOT
    - `&`：AND
    - `|`：OR
- 括弧 `()` で条件をまとめることも可能

```console:実行結果
> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "(!foo & fizz) | buzz"
...
test2@(bar, fizz)
test3@(fizz)
test4@(buzz)
```

## テストの実行順序
```java
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");
    }
}
```

```text:実行結果
ant
cat
dog
bear
```

デフォルトでは、テストメソッドの実行順序は意図的に非自明なアルゴリズムで決定されるようになっている[^1]。
というのも、本来単体テストは実行順序に依存しないことが望ましい。

[^1]: 要するに、ランダムではないが推測はできない順序付けがされるということだと思う

しかし、結合テストや機能テストのときは、実行順序が重要になる場合がありえる。[^8]

[^8]: マスタ登録してから個別機能を動かす、とか（たぶん）

そういったときに、テストの実行順序を制御するための仕組みが用意されている。

### アルファベット順
```java
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");
    }
}
```

```text:実行結果
ant
bear
cat
dog
```

- テストクラスに [@TestMethodOrder](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestMethodOrder.html) アノテーションを設定する
- `value` には、 [MethodOrderer](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/MethodOrderer.html) を実装したクラスの `Class` オブジェクトを指定する
    - `MethodOrder` は、メソッドの実行順序を制御する機能を提供する
- `MethodOrder` には、標準で３つの実装が用意されている
- [Alphanumeric](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html) は、メソッド名を [String.compareTo(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) で比較してソートする
    - メソッド名が同じ場合は、引数を含めて文字列で表現したものを比較する

### Order アノテーションで指定する
```java
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");
    }
}
```

```text:実行結果
dog
cat
bear
ant
```

- [OrderAnnotation](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html) を使用すると、 [@Order](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Order.html) アノテーションで順序を明示できる
- `@Order` が指定されていないメソッドは、デフォルトで `Integer.MAX_VALUE` が割り当てられる

### ランダム
```java
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");
    }
}
```

```text:実行結果
# １回目
ant
dog
cat
bear

# ２回目
bear
ant
cat
dog
```

- [Random](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/MethodOrderer.Random.html) を指定すると、メソッドの実行順序はランダムになる

## テストインスタンスのライフサイクル
### デフォルトのライフサイクル
```java
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());
    }
}
```

```text:実行結果
before@278240974
test1@278240974
before@370370379
test2@370370379
before@671046933
test3@671046933
```

- デフォルトは、テストメソッドが実行されるたびに新しいテストクラスのインスタンスが生成される

### ライフサイクルをテストクラスごとに変更する
```java
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());
    }
}
```

```text:実行結果
before@1504642150
test1@1504642150
before@1504642150
test2@1504642150
before@1504642150
test3@1504642150
```

- テストクラスに [@TestInstance](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestInstance.html) アノテーションを設定し、 `value` に [PER_CLASS](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestInstance.Lifecycle.html#PER_CLASS) を指定する
- すると、テストインスタンスがテストクラスごとに生成されるようになる
    - したがって、同一テストクラス内のテストメソッドは、全て同じインスタンスで実行されている
- なお、ライフサイクルを `PER_CLASS` にした場合、 `@BeforeAll` や `@AfterAll` はインスタンスメソッドに設定できるようになる

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

```text:実行結果
beforeAll()
staticBeforeAll()
test()
staticAfterAll()
afterAll()
```

- `static` メソッドにつけたままにしておくことも可能
- これのメリットは、 `@Nested` で入れ子にしたテストクラスにも `@BeforeAll` や `@AfterAll` をつけられるようになる点
    - `PER_CLASS` を指定しない場合、 `@BeforeAll`, `@AfterAll` はメソッドを `static` にしなければならない
    - しかし、 `@Nested` を設定したクラスは内部クラスなので、 Java の言語仕様上 `static` メソッドを定義できない
    - したがって、 `@Nested` クラスの中だけで最初と最後に実行したい処理が定義できなかった

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

```text:実行結果
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()
```

- `@Nested` の中だけで最初と最後に行う処理を定義できるようになった

### デフォルトのライフサイクルを変更する
- デフォルトはテストメソッドごとにテストインスタンスが生成される
- これを、デフォルトでテストクラスごとにインスタンスを生成させるように変更できる
- 方法は次のいずれか
    - システムプロパティで指定する
    - JUnit Platform 設定ファイルで指定する
- システムプロパティで指定する場合は、 `-Djunit.jupiter.testinstance.lifecycle.default=per_class` と指定する
- JUnit Platform 設定ファイルで指定する場合は、まずクラスパスルートに `junit-platform.properties` ファイルを配置する 
    - そして、このプロパティファイルで `junit.jupiter.testinstance.lifecycle.default=per_class` と指定する
- 推奨されるのは JUnit Platform 設定ファイルを使う方
    - システムプロパティを使用する場合、全ての実行環境で忘れずに指定しなければならなくなる
    - システムプロパティの設定し忘れで挙動が変わると、エラーの原因解明が難航するかもしれない

## 繰り返しテスト
```java
package sample.junit5;

import org.junit.jupiter.api.RepeatedTest;

class JUnit5Test {

    @RepeatedTest(3)
    void test() {
        System.out.println("test");
    }
}
```

**実行結果（ConsoleLauncher の場合）**

```text:実行結果(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          ]
```

**実行結果（Gradle の場合）**

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/3e7a9290-a113-16c2-c4a3-de994369f72b.jpeg)

- [@RepeatedTest](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/RepeatedTest.html) でメソッドをアノテートすると、 `value` で指定した回数だけテストが繰り返されるようになる
- 繰り返される各テストの表示名は、 `repetition <現在の繰り返し回数> of <総繰り返し回数>` になる

### 表示名を指定する
```java
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");
    }
}
```

**実行結果(ConsoleLauncher の場合)**

```text:実行結果(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]
```

**実行結果(Gradle の場合)**

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/6a23f824-06a9-6729-a928-cd2fe65d3bad.jpeg)

- `name` 属性で繰り返しテストの表示名を指定できる
- 表示名の指定では専用のプレースホルダが用意されていて、繰り返しの情報を表示名に反映することができる
    - `displayName` : テストメソッドの表示名
        - デフォルトはテストメソッドの名前
        - `@DisplayName` が指定されている場合は、そちらが使用される
    - `currentRepetition` : 現在の繰り返し回数（１始まり）
    - `totalRepetitions` : 総繰り返し回数
- 定義済みの表示名のパターンが `@RepetedTest` に用意されている
    - [LONG_DISPLAY_NAME](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/RepeatedTest.html#LONG_DISPLAY_NAME)
    - パターンは `"{displayName} :: repetition {currentRepetition} of {totalRepetitions}"`

### 繰り返しの情報をテストメソッドで受け取る
```java
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);
    }
}
```

```text:実行結果
before (1/3)
  test (1/3)
after (1/3)
before (2/3)
  test (2/3)
after (2/3)
before (3/3)
  test (3/3)
after (3/3)
```

- `@RepeatedTest` でアノテートされたテストメソッドは、現在の繰り返しの情報を保持した [RepetitionInfo](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/RepetitionInfo.html) オブジェクトを引数で受け取ることができる
- `@BeforeEach`, `@AfterEach` でも受け取ることができる
    - ただし、ここに `@Test` で宣言した通常のテストメソッドが混ざっていると、 `RepetitionInfo` が解決できずに実行時エラーになる
- `getCurrentRepetition()` で、現在の繰り返し回数を取得できる（１始まり）
- `getTotalRepetitions()` で、総繰り返し回数を取得できる

## パラメータ化テスト
```java
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);
    }
}
```

**実行結果(ConsoleLauncher の場合)**

```text:実行結果(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          ]
```

**実行結果(Gradle の場合)**

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/d4a9e21b-900d-f9d6-b151-6018caa93d88.jpeg)


- [@ParameterizedTest](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/ParameterizedTest.html) でアノテートされたメソッドは、テストで使用する値を引数で受け取りながら実行されるようになる
- 引数に渡す値を宣言する方法は複数用意されているが、ここでは [@ValueSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ValueSource.html) アノテーションを使用している
- `@ValueSource` は、 `strings` や `ints` などの属性でテストメソッドに渡すパラメータを静的に宣言できる
- パラメータを生成する元（↑の例の場合は `@ValueSource`）のことをパラメータの**ソース (Source)**と呼ぶ

### Enum をソースにする
```java
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
    }
}
```

```text:実行結果
value=HOGE
value=FUGA
value=PIYO
```

- [@EnumSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/EnumSource.html) アノテーションを使用すると、 `enum` に定義されている定数をパラメータとして使用できる
- `value` に `enum` の `Class` オブジェクトを渡すと、定義されている全ての定数がパラメータとして使用される

#### 特定の定数だけを使用する
```java
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
    }
}
```

```text:実行結果
value=HOGE
value=PIYO
```

- [names](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/EnumSource.html#names()) で使用する定数を絞ることができる

#### 特定の定数を除外する
```java
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
    }
}
```

```text:実行結果
value=FUGA
```

- [mode](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/EnumSource.html#mode()) で、 `names` で指定した条件をどのように適用するかを指定できる
- [EXCLUDE](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/EnumSource.Mode.html#EXCLUDE) を指定した場合は、 `names` で指定した定数が除外される

#### 正規表現で指定する
```java
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
    }
}
```

```text:実行結果
matchAll() value=FOURTH
matchAll() value=FIFTH
matchAny() value=FIRST
matchAny() value=FOURTH
matchAny() value=FIFTH
matchAny() value=SIXTH
```

- [MATCH_ALL](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/EnumSource.Mode.html#MATCH_ALL) または [MATCH_ANY](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/EnumSource.Mode.html#MATCH_ANY) を使用すると、正規表現で対象の定数を絞り込むことができる
- `MATCH_ALL` は、全ての条件に一致した定数だけに絞り込むことができる
- `MATCH_ANY` は、いずれかの条件に一致した定数だけに絞り込むことができる

### メソッドをソースにする
```java
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");
    }
}
```

```text:実行結果
value=hoge
value=fuga
value=piyo
```

- [@MethodSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/MethodSource.html) を使用すると、メソッドをパラメータのソースにできる
- デフォルトでは、テストメソッドと同じ名前で引数を持たない `static` メソッドがソースとして自動的に選択される
- ソースメソッドは、パラメータとして渡す値を「[Stream](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/Stream.html) に変換できる型」で返す必要がある
    - 何が「`Stream` に変換できる型」なのかは、 `MethodSource` の Javadoc やユーザーガイドに次のように記載してある
    - `Stream`, `DoubleStream`, `LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, オブジェクトの配列、プリミティブ型の配列
    - ということで、たいていの**それっぽい型**は良しなにしてくれる感じ

#### ソースメソッドを明示する
```java
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");
    }
}
```

```text:実行結果
value=HOGE
value=FUGA
value=PIYO
```

- `@MethodSource` の `value` でソースメソッドの名前を明示することができる

#### 他のクラスのメソッドをソースにする
```java:SourceClass
package sample.junit5;

import java.util.List;

class SourceClass {
    static List<String> createSource() {
        return List.of("foo", "bar");
    }
}
```

```java: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);
    }
}
```

```text:実行結果
value=foo
value=bar
```

- `@MethodSource` の `value` で、`クラスの完全修飾名#メソッド名` と指定することで、外部のクラスのメソッドをソースにできる

#### １回のパラメータで複数の仮引数を渡す
```java
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)
        );
    }
}
```

```text:実行結果
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
```

- １回のパラメータで複数の仮引数に値を渡すこともできる
- その場合、ソースメソッドは `Object[]` の `Stream` （にできるもの）を返すように実装しなければならない
    - この例では `List<Object[]>` を返すようにしている
- jupiter が用意している [Arguments](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html) という専用の入れ物クラスを使うこともできる
    - [arguments(Object...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html#arguments(java.lang.Object...)) または [of(Object...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html#of(java.lang.Object...)) というファクトリメソッドが用意されているので、それを使ってインスタンスを生成できる

#### CSV テキストをソースにする
```java
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);
    }
}
```

```text:実行結果
s1=[foo], s2=[bar], i=[1]
s1=[hoge,fuga], s2=[], i=[2]
s1=[fizz], s2=[null], i=[3]
```

- [@CsvSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/CsvSource.html) を使用すると、静的な CSV 形式のテキストをソースにできる
- 引用符にはシングルクォーテーション(`'`)を使用する
- `''` は空文字に、完全な空白は `null` として処理される

#### CSV ファイルをソースにする
```text:フォルダ貢税
`-src/test/
  |-resources/
  | `-test.csv
  `-java/
    `-sample/junit5/
      `-JUnit5Test.java
```

```csv:test.csv
hoge,1
fuga,2
piyo,3
```

```java: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);
    }
}
```

```text:実行結果
string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]
```

- [@CsvFileSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/CsvFileSource.html) を使用すると、クラスパス内の CSV ファイルをソースに指定できる
- `resources` で、使用する CSV ファイルのパスを指定する
- エンコーディングや改行コードなどは、アノテーションの属性で指定できる（Javadoc 参照）

#### 再利用可能なソースクラスを作成する
```java: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)
        );
    }
}
```

```java: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);
    }
}
```

```text:実行結果
string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]
```

- [@ArgumentsSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ArgumentsSource.html) を使用すると、 [ArgumentsProvider](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ArgumentsProvider.html) を実装したクラスをソースとして利用できる
- `value` に `ArgumentsProvider` を実装したクラスの `Class` オブジェクトを指定する
- 複数のテストクラスでソースを再利用する場合に活用できる

### パラメータの変換
#### 拡大変換
```java
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));
    }
}
```

```text:実行結果
i=10, l=20, d=30.0
```

- パラメータはプリミティブの拡大変換(**widening primitive conversions**)に対応している
    - `byte -> short` や `int -> long` などのように、より大きなサイズのプリミティブ型に変換する仕組み
- パラメータの実体が `int` でも、テストメソッドの引数は `long` や `double` で受けることができる


#### 暗黙的な変換
```java
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
    }
}
```

```text:実行結果
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
```

- パラメータが文字列であっても、テストメソッドの仮引数の型から暗黙的に型変換を行ってくれる
- 様々な型に対応していて、標準 API に存在するよく使う型はだいたいサポートしている

##### 暗黙的な型変換がサポートされている型と変換方法

|変換後の型|変換方法|
|---|---|
|`boolean`/`java.lang.Boolean`|[Boolean.valueOf(String)[^4]](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Boolean.html#valueOf(java.lang.String))|
|`char`/`java.lang.Character`|[String.charAt(0)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/String.html#charAt(int))[^3]|
|`byte`/`java.lang.Byte`|[Byte.decode(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Byte.html#decode(java.lang.String))|
|`short`/`java.lang.Short`|[Short.decode(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Short.html#decode(java.lang.String))|
|`int`/`java.lang.Integer`|[Integer.decode(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Integer.html#decode(java.lang.String))|
|`long`/`java.lang.Long`|[Long.decode(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Long.html#decode(java.lang.String))|
|`float`/`java.lang.Float`|[Float.valueOf(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Float.html#valueOf(java.lang.String))|
|`double`/`java.lang.Double`|[Double.valueOf(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Double.html#valueOf(java.lang.String))|
|任意の `enum` 型（`java.lang.Enum` のサブクラス）|[Enum.valueOf(Class, String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Enum.html#valueOf(java.lang.Class,java.lang.String))|
|`java.time.Duration`|[Duration.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence))|
|`java.time.Instant`|[Instant.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/Instant.html#parse(java.lang.CharSequence))|
|`java.time.LocalDate`|[LocalDate.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/LocalDate.html#parse(java.lang.CharSequence))|
|`java.time.LocalDateTime`|[LocalDateTime.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/LocalDateTime.html#parse(java.lang.CharSequence))|
|`java.time.LocalTime`|[LocalTime.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/LocalTime.html#parse(java.lang.CharSequence))|
|`java.time.MonthDay`|[MonthDay.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/MonthDay.html#parse(java.lang.CharSequence))|
|`java.time.OffsetDateTime`|[OffsetDateTime.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/OffsetDateTime.html#parse(java.lang.CharSequence))|
|`java.time.OffsetTime`|[OffsetTime.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/OffsetTime.html#parse(java.lang.CharSequence))|
|`java.time.Period`|[Period.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/Period.html#parse(java.lang.CharSequence))|
|`java.time.Year`|[Year.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/Year.html#parse(java.lang.CharSequence))|
|`java.time.YearMonth`|[YearMonth.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/YearMonth.html#parse(java.lang.CharSequence))|
|`java.time.ZonedDateTime`|[ZonedDateTime.parse(CharSequence)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/ZonedDateTime.html#parse(java.lang.CharSequence))|
|`java.time.ZoneId`|[ZoneId.of(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/ZoneId.html#of(java.lang.String))|
|`java.time.ZoneOffset`|[ZoneOffset.of(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/ZoneOffset.html#of(java.lang.String))|
|`java.io.File`|[new File(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/File.html#%3Cinit%3E(java.lang.String))|
|`java.nio.charset.Charset`|[Charset.forName(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/charset/Charset.html#forName(java.lang.String))|
|`java.nio.file.Path`|[Paths.get(String, String...)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/Paths.html#get(java.lang.String,java.lang.String...))|
|`java.net.URI`|[URI.create(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/net/URI.html#create(java.lang.String))|
|`java.net.URL`|[new URL(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/net/URL.html#%3Cinit%3E(java.lang.String))|
|`java.math.BigDecimal`|[new BigDecimal(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/math/BigDecimal.html#%3Cinit%3E(java.lang.String))|
|`java.math.BigInteger`|[new BigInteger(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/math/BigInteger.html#%3Cinit%3E(java.lang.String))|
|`java.util.Currency`|[Currency.getInstance(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Currency.html#getInstance(java.lang.String))|
|`java.util.Locale`|[new Locale(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Locale.html#%3Cinit%3E(java.lang.String))|
|`java.util.UUID`|[UUID.fromString(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/UUID.html#fromString(java.lang.String))|
|`java.lang.Class`|※詳細後述|

[^3]: 元の `String` の `length()` が `1` でない場合はエラー
[^4]: `null` はエラー

##### java.lang.Class の変換
```java
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 {}
}
```

```text:実行結果
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
```

- プリミティブ型は、型の名前をそのまま指定できる(`int`, `long`, `float`, etc...)
- プリミティブ型の配列は、 `int[]`, `int[][]` のような表記で指定できる
- 配列は [Class.getName()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Class.html#getName()) で得られる文字列と同じ形式でも指定できる(`[[I`, `[Ljava.lang.String;`, etc...)
- その他の参照型は、[バイナリ名](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ClassLoader.html#binary-name)で指定する（内部的には [ClassLoader.loadClass(String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ClassLoader.html#loadClass(java.lang.String)) が使用される）

## 動的テスト
```java
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!!"))
        );
    }
}
```

```text:実行結果
Dynamic Hoge!!
Dynamic Fuga!!

...
.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- testFactory() [OK]
      +-- Hoge [OK]
      '-- Fuga [OK]
```

- [@TestFactory](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestFactory.html) アノテーションを使うことで、テストケースを動的に生成できる
- `@TestFactory` でアノテートされたメソッドは、 [DynamicNode](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicNode.html) のコレクションを返すように実装する
    - 「コレクション」というのは、厳密には次のいずれかであればいい
        - `java.util.Collection`
        - `java.lang.Iterable`
        - `java.util.Iterator`
        - `java.util.stream.Stream`
        - 配列
    - `Stream` で返した場合は Jupiter 側で `close()` してくれるので、 `Files.lines()` で生成した `Stream` を使ったりしていても安全
- `DynamicNode` 自体は抽象クラスなので、実際はサブクラスの [DynamicTest](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicTest.html) か [DynamicContainer](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicContainer.html) のいずれかを使用する
    - 上記例では [dynamicTest(String, Executable)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicTest.html#dynamicTest(java.lang.String,org.junit.jupiter.api.function.Executable)) というファクトリメソッドを使って `DynamicTest` のインスタンスを生成している
    - 第一引数はテストの名前
    - 第二引数はテストの内容

### 動的テストのライフサイクル
```java
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()");
    }
}
```

```text:実行結果
beforeEach()
  testFactory()
    Dynamic Hoge!!
    Dynamic Fuga!!
afterEach()
beforeEach()
  test()
afterEach()
```

- `@BeforeEach` や `@AfterEach` は、 `@TestFactory` が設定されたメソッドの前後で実行されるだけで、各動的テストの前後では実行されない

### 動的テストを入れ子構造にする
```java
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."))
            ))
        );
    }
}
```

```text:実行結果
Dynamic Foo.
Dynamic Hoge.
Dynamic Fuga.
Dynamic Fizz.
Dynamic Buzz.

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- testFactory() [OK]
      +-- Dynamic Container 1 [OK]
      | +-- Foo [OK]
      | '-- Dynamic Container 1-1 [OK]
      |   +-- Hoge [OK]
      |   '-- Fuga [OK]
      '-- Dynamic Container 2 [OK]
        +-- Fizz [OK]
        '-- Buzz [OK]
```

- [DynamicContainer](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicContainer.html) を使うと、動的テストを入れ子構造にできる

## 並列実行
```properties:junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
```

- `junit-platform.properties` はクラスパスルートに配置している

```java
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) // ★これを忘れずにつける！
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);
    }
}
```

```text:実行結果
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
```

- デフォルトでは、全てのテストメソッドは単一のスレッドで順次実行される
- 設定ファイル（クラスパスルートに配置した `junit-platform.properties`）で `junit.jupiter.execution.parallel.enabled=true` と設定すると、並列実行が有効になる
- ただし、この設定だけではテストメソッドは変わらず単一スレッドで順次実行される
- テストメソッドを並列実行させるためには、テストクラスに [@Execution](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/parallel/Execution.html) アノテーションを設定して `value` に [CONCURRENT](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/parallel/ExecutionMode.html#CONCURRENT) を指定する必要がある
- [SAME_THREAD](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/parallel/ExecutionMode.html#SAME_THREAD) を指定した場合は親と同じスレッドで実行される
    - デフォルトはこちらの設定になっている

### デフォルトの実行モードを変更する
```properties:junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
```

```java
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);
    }
}
```

```text:実行結果
test1()@ForkJoinPool-1-worker-5
test2()@ForkJoinPool-1-worker-7
```

- 設定ファイルで `junit.jupiter.execution.parallel.mode.default=concurrent` と指定すると、デフォルトの実行モードが並列実行（`ExecutionMode.CONCURRENT` を指定した状態）になる

#### デフォルトの実行モードが適用されない例外
- `junit.jupiter.execution.parallel.mode.default=concurrent` を指定すると、ほとんどのテストメソッドは並列実行されるようになる
- ただし、例外として次の設定がされたテストクラスやメソッドは、デフォルトの実行モードを変更しても並列実行されないようになっている
    - `Lifecycle.PER_CLASS` を指定したテストクラス
    - `MethodOrderer.Random` 以外の `MethodOrderer` が指定されたテストメソッド

```java:デフォルトの実行モードを変更しても並列実行されない例
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);
    }
}
```

```text:実行結果
StandardNestedTest.test1()@ForkJoinPool-1-worker-15
StandardNestedTest.test2()@ForkJoinPool-1-worker-13
PerClassTest.test1()@ForkJoinPool-1-worker-7
OrderedTest.test1()@ForkJoinPool-1-worker-5
PerClassTest.test2()@ForkJoinPool-1-worker-7
OrderedTest.test2()@ForkJoinPool-1-worker-5
```

- `StandardNestedTest` のテストメソッドは異なるスレッドで実行されている
- しかし、 `PerClassTest` と `OrderedTest` のテストメソッドは、それぞれ同じスレッドで実行されている
- これらのクラスのテストメソッドも並列実行させたい場合は[^5]、そのテストクラスがスレッドセーフであることを確認したうえで、明示的に `@Execution(ExecutionMode.CONCURRENT)` を指定する

[^5]: `@TestMethodOrder` でわざわざ順序を指定しているテストを並列実行させるというは変な話だが

```java:明示的に並列実行を指定する
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) {...}
}
```

```text:実行結果
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
```

### トップレベルのクラスのデフォルト実行モードを変更する
```properties:junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.classes.default=concurrent
```

```java: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);
    }
}
```

```java: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);
    }
}
```

```text:実行結果
BarTest.test1()@ForkJoinPool-1-worker-3
FooTest.test1()@ForkJoinPool-1-worker-7
BarTest.test2()@ForkJoinPool-1-worker-3
FooTest.test2()@ForkJoinPool-1-worker-7
```

- 設定ファイルで `junit.jupiter.execution.parallel.mode.classes.default=concurrent` と指定すると、トップレベルのクラスのデフォルトの実行モードだけを変更できる
- 先程設定していた `junit.jupiter.execution.parallel.mode.default=concurrent` はメソッドレベルの並列実行を制御するもので、今回はその設定をしていない（したがって、デフォルトの `same_thread` を指定しているのと同じ状態）
- この結果、テストクラス単位で別スレッドで実行し、テストクラス内のメソッドは単一スレッドで順次実行されるようになっている
    - `FooTest` と `BarTest` は異なるスレッドで実行されている（`ForkJoinPool-1-worker-7` と `ForkJoinPool-1-worker-3`）
    - しかし、 `FooTest` 内の各メソッドは１つのスレッド（`ForkJoinPool-1-worker-7`）で実行されている
- `junit.jupiter.execution.parallel.mode.default` と `junit.jupiter.execution.parallel.mode.classes.default` の設定の組み合わせは全部で４通りあり、それぞれ次のような動作になる

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/9673a295-84b4-1ac4-8350-1892a68986c3.jpeg)

- なお、 `junit.jupiter.execution.parallel.mode.classes.default` （クラスごとのデフォルト動作）が未指定だった場合は、 `junit.jupiter.execution.parallel.mode.default` と同じ設定値になる

### 同時並行数を調整する
#### プロセッサ（コア）数に合わせて動的に変更する
```properties: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
```

```java: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();
    }
}
```

- 同時実行されているスレッドの数をカウントするためのクラス

```java: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);
    }
}
```

- 動的テストの仕組みを使って、処理が１秒かかるテストを 20 個作成している
- テスト終了後に、各種情報を出力している


```text:実行結果
2033ms
Available Processors = 8
Max Parallelism Count = 16
Active Thread Count = 18
[0] main
[1] ForkJoinPool-1-worker-19
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-23
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-27
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-31
[8] ForkJoinPool-1-worker-17
[9] ForkJoinPool-1-worker-3
[10] ForkJoinPool-1-worker-21
[11] ForkJoinPool-1-worker-7
[12] ForkJoinPool-1-worker-25
[13] ForkJoinPool-1-worker-11
[14] ForkJoinPool-1-worker-29
[15] ForkJoinPool-1-worker-15
[16] ForkJoinPool-1-worker-1
[17] ForkJoinPool-1-worker-33
```

- 設定ファイルで `junit.jupiter.execution.parallel.config.dynamic.factor` を指定することで、並列実行するスレッドの数を調整できる
    - `factor` に指定した値に実行環境のプロセッサ数（コア数）を掛けた数が、並列実行されるスレッド数になる
    - 検証した環境はコア数が８で `factor` に２を指定していたので、並列実行されたスレッド数は１６となっている
    - なお、並列実行の実行には [ForkJoinPool](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html) が使われており、プールされるスレッド数は並列実行されるスレッド数よりも多くなることがある（アクティブなスレッド数が並列実行数よりも多くなっている）
- `factor` が指定されていない場合、デフォルトは１になる
- ちなみに、 `factor` は裏で `BigDecimal` に変換されているので、少数を指定することもできる
    - 小数点以下は切り捨て（最終的に [intValue()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/math/BigDecimal.html#intValue()) で `int` に変換してる）

```properties: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
```

```text:実行結果
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
```


#### 固定値にする
```properties:junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=6
```

※テストの実装は `dynamic` を指定したときのものと同じ

```text:実行結果
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
```

- `junit.jupiter.execution.parallel.config.strategy` に `fixed` を指定すると、固定の並列数を指定できるようになる
- 並列数は `junit.jupiter.execution.parallel.config.fixed.parallelism` で指定する

#### 任意にカスタマイズする
```gradle: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` はデフォルトでは実行時だけしか参照できないので、コンパイル時も参照できるように `testImplementation` に指定している

```java: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;
            }
        };
    }
}
```

- `ParallelExecutionConfigurationStrategy` を実装したクラスを作成する
- `createConfiguration()` メソッドで `ParallelExecutionConfiguration` を実装したインスタンスを返すようにする
- `ParallelExecutionConfiguration` に定義されている Getter は、 `ForkJoinPool` を生成するときに使用される
- 各値の意味は、 `ForkJoinPool` の[コンストラクタの Javadoc](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html#%3Cinit%3E(int,java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory,java.lang.Thread.UncaughtExceptionHandler,boolean,int,int,int,java.util.function.Predicate,long,java.util.concurrent.TimeUnit)) を参照


```properties: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
```

- `junit.jupiter.execution.parallel.config.strategy` で、 `custom` を指定する
- `junit.jupiter.execution.parallel.config.custom.class` で、自作した `ParallelExecutionConfigurationStrategy` の実装クラスを指定する

※テストの実装は `dynamic` のときと同じ

```text:実行結果
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
```

- `MyParallelExecutionConfigurationStrategy` が返した `ParallelExecutionConfiguration` の設定に従って並列実行されていることが分かる

### 排他制御
```java:排他制御がない場合
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);
    }
}
```

- クラスを並列実行にして、 `static` 変数 `n` を３つのテストメソッドから１万回ずつインクリメントしている

```text:実行結果
begin test3
begin test1
begin test2
end test1
end test2
end test3
n = 13394
```

- 当然、同期が取られていないので結果は 30,000 にはならない

```java:排他制御を入れた場合
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() { ... }
}
```

- 各メソッドに `@ResourceLock` アノテーションを設定する

```text:実行結果
begin test1
end test1
begin test2
end test2
begin test3
end test3
n = 30000
```

- 各メソッドの実行で同期が取られて、 `n` の値は 30,000 になった
- [@ResourceLock](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/parallel/ResourceLock.html) をクラスやメソッドに設定すると、その中のテストケースは同期がとられるようになる
- `value` には排他を制御するためのキーとなる文字列を指定する
    - 同じキー文字列が設定された `@ResourceLock` 間で同期がとられる

#### アクセスモードを指定する
```java
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);
    }
}
```

```text:実行結果
begin test1(READ_WRITE)
end test1(READ_WRITE)
begin test2(READ)
begin test3(READ)
end test2(READ)
end test3(READ)
```

- `@ResourceLock` の `mode` で、アクセスモードを指定できる
    - アクセスモードは [ResourceAccessMode](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/parallel/ResourceAccessMode.html) で定義された定数で指定する
- 各アクセスモードによる排他制御の組み合わせは次のような感じになる

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/b830b878-d37a-4ad7-83a0-fd4f20209719.jpeg)

- `READ` 同士なら並列実行可能だが、 `READ_WRITE` がからむと排他制御される
- `READ` はデータの読み取りのみで更新を行わないテストメソッドに指定し、 `READ_WRITE` はデータの更新を行うテストメソッドに指定する感じ

## インターフェースのデフォルトメソッドを利用する
```java: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()");
    }
}
```

```java:JUnit5Test
package sample.junit5;

class JUnit5Test implements DefaultMethodTest {}
```

```text:実行結果
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()
```

- インターフェースのデフォルトメソッドでもテストを定義できる
    - インターフェースを実行対象となるクラスで `implements` すれば、テストが実行される


# 拡張モデル
JUnit Jupiter には**拡張モデル**と呼ばれる仕組みが用意されており、任意の拡張機能を簡単に導入できるようになっている。

## Hello World
```java: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()");
    }
}
```

- 拡張機能を作るには、まずは [Extension](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/Extension.html) インターフェースを実装したクラスを作成する
- ただし、 `Extension` 自体はマーカーインターフェースでメソッドは定義されていない
    - 実際には、 `Extension` を継承した [BeforeEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html) や [AfterEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterEachCallback.html) といった拡張ポイントごとに定義されたサブインターフェースを実装する
    - `BeforeEachCallback` なら、各テスト前にコールバックされる `beforeEach()` メソッドが定義されており、  
      `AfterEachCallback` なら、各テスト後にコールバックされる `afterEach()` メソッドが定義されている


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

- 拡張機能を実装したクラスを実際にテストで使用する方法の１つとして、 [@ExtendWith](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtendWith.html) アノテーションを使う方法がある
- 拡張機能を適用したい場所に `@ExtendWith` アノテーションを設定し、 `value` に適用したい拡張機能の `Class` オブジェクトを指定する

```text:実行結果
MyExtension.beforeEach()
  test1()
MyExtension.afterEach()
MyExtension.beforeEach()
  test2()
MyExtension.afterEach()
```

- これだけで、各テストの前後で拡張機能で定義した処理が実行されるようになった
- なお、例ではクラスに対して `@ExtendWith` を指定したが、メソッドに指定することで部分的に拡張機能を適用することもできる


```java:test1()メソッドにだけ拡張機能を適用した場合
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()");
    }
}
```

```text:実行結果
MyExtension.beforeEach()
  test1()
MyExtension.afterEach()
test2()
```

## 拡張ポイント
`Extension` インターフェースを継承した拡張ポイントとなるインターフェースには次のようなものがある。

|インターフェース|説明|
|---|---|
|[ExecutionCondition](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExecutionCondition.html)|テストを実行するかどうかを制御する。|
|[TestInstanceFactory](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestInstanceFactory.html)|テストインスタンスを生成する。|
|[TestInstancePostProcessor](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html)|テストインスタンス生成後の初期化処理などを行う。|
|[ParameterResolver](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ParameterResolver.html)|テストメソッドやライフサイクルメソッドなどの引数を解決する。|
|[BeforeAllCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeAllCallback.html)<br>[BeforeEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html)<br>[BeforeTestExecutionCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html)<br>[AfterTestExecutionCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterTestExecutionCallback.html)<br>[AfterEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterEachCallback.html)<br>[AfterAllCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterAllCallback.html)|テストの実行前後などライフサイクルに沿った処理を実行する。|
|[TestWatcher](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestWatcher.html)|テストメソッドの実行結果に合わせた後処理を実行する。|
|[TestExecutionExceptionHandler](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html)|テスト実行時に投げられた例外をハンドリングする。|
|[LifecycleMethodExecutionExceptionHandler](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.html)|`@BeforeEach` などのライフサイクルメソッドで投げられた例外をハンドリングする。|
|[TestTemplateInvocationContextProvider](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html)|同じテストを異なるコンテキストで実行するための準備処理などを行う。|

## サポートクラス
拡張クラスを実装するときに、汎用的に利用できるユーティリティクラス（サポートクラス）が提供されている。

- [AnnotationSupport](https://junit.org/junit5/docs/current/api/org/junit/platform/commons/support/AnnotationSupport.html)
    - アノテーションに関するユーティリティクラス
        - アノテートされた要素の検索
        - アノテートされているかの判定
        - などなど
- [ClassSupport](https://junit.org/junit5/docs/current/api/org/junit/platform/commons/support/ClassSupport.html)
    - `Class` の文字列表現に関する処理を提供するユーティリティ
- [ReflectionSupport](https://junit.org/junit5/docs/current/api/org/junit/platform/commons/support/ReflectionSupport.html)
    - リフレクション操作に関するユーティリティ
        - クラス・メソッド・フィールド・コンストラクタの検索
        - メソッド・コンストラクタの実行
        - インスタンスの生成
        - などなど
- [ModifierSupport](https://junit.org/junit5/docs/current/api/org/junit/platform/commons/support/ModifierSupport.html)
    - 修飾子の判定メソッドを定義したユーティリティ

例外処理や面倒な記述を省き、簡潔な方法で操作ができるようになっている。

とりあえず「こういうものが存在する」ということを頭の片隅に入れておいて、いざ拡張クラスを作り始めたら「あ、これサポートクラス使えるんじゃね？」と考えて、欲しいメソッドがあるか探す感じでいいと思う。

これらのクラスは内部ユーティリティではなく、第三者が独自の `TestEngine` や拡張機能を作るときの補助として用意されているものなので安心して使って問題ない。

## テストの実行条件を制御する
```java: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.");
        }
    }
}
```

- テストメソッドの表示名に `"o"` が含まれているものだけ有効にしている

```java: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"))
        );
    }
}
```

```text:実行結果
# Test class = JUnit5Test
# Test method = testFactory
beforeEach()
  dynamicTest1
  dynamicTest2
# Test method = fuga
# Test method = hoge
beforeEach()
  hoge()
# Test class = NestedClass
# Test method = piyo
beforeEach()
  piyo()
```

- [ExecutionCondition](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExecutionCondition.html) を使用すると、テストを実行するかどうかを制御できる
- コンテナ[^6]およびメソッドごとに `evaluateExecutionCondition(ExtensionContext)` メソッドがコールされるので、対象のテストを実行するかどうかを戻り値で制御する
    - 実行する場合は [ConditionEvaluationResult.enabled(String)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ConditionEvaluationResult.html#enabled(java.lang.String)) で生成した値を返す
    - 実行しない場合は [ConditionEvaluationResult.disabled(String)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ConditionEvaluationResult.html#disabled(java.lang.String)) で生成した値を返す
    - 引数には、対象を有効/無効にした理由を記述する
- `evaluateExecutionCondition(ExtensionContext)` メソッドが引数で受け取る [ExtensionContext](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html) を使うと、対象のテストメソッドやコンテナについての情報を参照できる
    - [getTestMethod()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html#getTestMethod()) など、一部のメソッドは条件次第で値が存在しない可能性があるため、戻り値の型が `Optional` になっている
    - 必ず `null` にならないことが分かっているのであれば、 [getRequiredTestMethod()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html#getRequiredTestMethod()) のように `Required` がついたメソッドを使う手もある

[^6]: ドキュメントでは `container` と表現されているが、 `container` が具体的に何を指しているのかは書かれていない（たぶん、テストクラスや動的テストの `DynamicContainer` などのテストメソッドをまとめたカタマリを指していると思う）

## テストインスタンスを生成する
```java: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;
                });
    }
}
```

```java: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() + "]"))
        );
    }
}
```

```text:実行結果
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [1781155104]
JUnit5Test.dynamicTest1() [1781155104]
JUnit5Test.dynamicTest2() [1781155104]
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [667449440]
JUnit5Test.test() [667449440]
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [1846668301]
===========================================
* testClass=class sample.junit5.JUnit5Test$NestedClass
* outerInstance=sample.junit5.JUnit5Test@6e11ec0d
* outerInstance [1846668301]
* testInstance [1282836833]
NestedClass.test() [1282836833]
```

- [TestInstanceFactory](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestInstanceFactory.html) を使用すると、テストインスタンスの生成をマニュアル化できる
- 各テストメソッドが実行される前に `createTestInstance()` メソッドがコールされる
    - ※ライフサイクルを `PER_CLASS` にしている場合は、クラスごとに１回だけコールされる
- `createTestInstance()` が返したインスタンスがテストで利用される
- `@Nested` で定義した内部テストクラスがある場合は、一旦外側のクラスの生成のためにコールされたあとで、内部テストクラス生成のためにもう一度メソッドがコールされる
    - 内部クラスのときは `TestInstanceFactoryContext` の `getOuterInstance()` が空でなくなるので、それで判断できる

## テストインスタンス生成後の初期化処理を行う
```java: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());
    }
}
```

```java: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() + "]");
    }
}
```

```text:実行結果
testInstance.hash = 756008141
test1() [756008141]
testInstance.hash = 282044315
test2() [282044315]
```

- [TestInstancePostProcessor](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html) を使用すると、テストメソッドが実行される前にテストインスタンスを受け取って任意の処理を実行することができる
- テストインスタンスに何か依存性を注入したり、初期化メソッドを呼んだりするのに使うらしい
- `BeforeEachCallback` でよくね？
    - `BeforeEachCallback` だと、テストインスタンスを `ExtensionContext` から取得することになるから、 `Optional` になったり `getRequiredInstance()` になったいするのがアレなのかな
    - あとは、「テストインスタンスに対する後処理を行う」という実装の意図を明確にできるというメリットがあるのかもしれない（憶測）

## パラメータを解決する
```java: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;
        }
    }
}
```

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

```text:実行結果
target=sample.junit5.JUnit5Test@5e101011, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test@5e101011, executable=dynamicTest, index=0, parameter=string
dynamicTest(string=Hello)
-----------------------------------------
target=sample.junit5.JUnit5Test@4d3b0c46, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=0, parameter=string
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=1, parameter=d
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=2, parameter=i
test1(string=Hello, d=12.340000, i=999)
-----------------------------------------
target=sample.junit5.JUnit5Test@7d9ea927, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test$NestedClass@710edef, executable=test2, index=0, parameter=d
test2(d=12.340000)
-----------------------------------------
```

- [ParameterResolver](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ParameterResolver.html) を使用すると、各種メソッドの引数を任意に解決できるようになる
    - `@Test` だけでなく、 `@TestFactory` や `@BeforeEach` といったメソッドの引数も解決できる
- 各メソッド引数ごとに `supportsParameter()` がコールされる
    - [ParameterContext](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ParameterContext.html) から、引数のメタ情を参照できる
    - 引数の解決をサポートする場合は `true` を返し、そうでない場合は `false` を返すように実装する
- `supportsParameter()` が `true` を返した場合、次に `resolveParameter()` がコールされる
    - このメソッドで、解決した引数の値を返すように実装する
- なお、 `Parameter.getName()` で引数名を取得するためには、 `javac` でコンパイルするときに `-parameters` オプションをつけておく必要がある点に注意
    - オプションがない場合は、 `arg0`, `arg1` のような名前になる
    - Gradle でビルドしている場合は、 `compileTestJava.options.complierArgs += "-parameters"` みたいな感じで設定できる

## ライフサイクルに沿った処理を行う
```java: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");
    }
}
```

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

```text:実行結果
BeforeAllCallback
  beforeAll()
    BeforeEachCallback
      beforeEach()
        BeforeTestExecutionCallback
          test()
        AfterTestExecutionCallback
      afterEach()
    AfterEachCallback
  afterAll()
AfterAllCallback
```

- [BeforeAllCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeAllCallback.html)
    - `@BeforeAll` より前に実行する処理を実装できる
- [BeforeEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html)
    - `@BeforeEach` より前に実行する処理を実装できる
- [BeforeTestExecutionCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html)
    - `@BeforeEach` より後で、テストメソッドより前に実行する処理を実装できる
- [AfterTestExecutionCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterTestExecutionCallback.html)
    - `@AfterEach` より前で、テストメソッドより後に実行する処理を実装できる
- [AfterEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterEachCallback.html)
    - `@AfterEach` より後で実行する処理を実装できる
- [AfterAllCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterAllCallback.html)
    - `@AfterAll` より後で実行する処理を実装できる

## テスト結果に合わせた処理を行う
```java: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");
    }
}
```

```java: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");
    }
}
```

```text:実行結果
testAborted()
AfterEachCallback
aborted : test=testAborted(), cause=org.opentest4j.TestAbortedException: Assumption failed: test abort

testSuccessful()
AfterEachCallback
successful : test=testSuccessful()

disabled : test=testDisabled(), reason=REASON

testFailed()
AfterEachCallback
failed : test=testFailed(), cause=org.opentest4j.AssertionFailedError: expected: <10> but was: <20>
```

- [TestWatcher](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestWatcher.html) を使用すると、テスト結果に合わせた処理を実装できる
- `TestWatcher` には次の４つのメソッドが定義されている
    - `testDisabled()`
        - テストが無効だったときに実行される
    - `testSuccessful()`
        - テストが成功したときに実行される
    - `testAborted()`
        - テストが中断（`assumeThat()` など）されたときに実行される
    - `testFailed()`
        - テストが失敗したときに実行される
- 各メソッドは中身が空の `default` メソッドとして定義されている
    - したがって、デフォルトでは何も処理が行われない
    - 必要に応じて各メソッドをオーバーライドして具体的な実装を記述する
- 各メソッドは、 `AfterEachCallback` よりも後に実行される

## テストでスローされた例外を処理する
```java: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");
        }
    }
}
```

- `NullPointerException` を受け取った場合は、そのまま再スロー
- `IllegalStateException` を受け取った場合は、 `UnsupportedOperationException` をスロー
- それ以外は何もせずに終了


```java: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");
    }
}
```

```text:実行結果
success()
fail()
 * throwable=org.opentest4j.AssertionFailedError: expected: <10> but was: <20>
throwsNullPointerException()
 * throwable=java.lang.NullPointerException: test
throwsIllegalStateException()
 * throwable=java.lang.IllegalStateException: test
throwsIOException()
 * throwable=java.io.IOException: test
.
'-- JUnit Jupiter [OK]
  +-- JUnit5Test [OK]
  | +-- success() [OK]
  | +-- fail() [OK]
  | +-- throwsNullPointerException() [X] test
  | +-- throwsIllegalStateException() [X] test
  | '-- throwsIOException() [OK]
  '-- ParallelismCheck [S] class sample.junit5.ParallelismCheck is @Disabled

Failures (2):
  JUnit Jupiter:JUnit5Test:throwsNullPointerException()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'throwsNullPointerException', methodParameterTypes = '']
    => java.lang.NullPointerException: test
       ...

  JUnit Jupiter:JUnit5Test:throwsIllegalStateException()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'throwsIllegalStateException', methodParameterTypes = '']
    => java.lang.UnsupportedOperationException: test

```

- [TestExecutionExceptionHandler](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html) を使うと、テストで発生した例外をハンドリングできる
- テストで例外がスローされると、 `handleTestExecutionException()` がコールされる
    - 第二引数でスローされた例外を受け取ることができる
    - このメソッドが例外をスローすればテストは失敗になる
    - 何も例外をスローせずに終了した場合は、テストは成功になる（例外が握りつぶされる）
    - 受け取った例外とは別の例外を投げることも可能
- アサーションエラー（`AssertionFailedError`）も対象なので要注意
    - 再スローを忘れてると、アサーションエラーも握りつぶしてしまう

## ライフサイクルメソッドでスローされた例外を処理する
```java: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);
    }
}
```

```java: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);
    }
}
```

```java: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("普通にライフサイクルメソッドで例外がスローされた場合")
        void test() {
            System.out.println("InnerClass1");
        }
    }

    @Nested
    @ExtendWith(MyTestExecutionExceptionHandler.class)
    class InnerClass2 {

        @Test
        @DisplayName("TestExecutionExceptionHandler はライフサイクルメソッドでスローされた例外に対してどう動くか")
        void test() {
            System.out.println("InnerClass2");
        }
    }

    @Nested
    @ExtendWith(MyLifecycleMethodExecutionExceptionHandler.class)
    class InnerClass3 {

        @Test
        @DisplayName("ライフサイクルメソッドでスローされた例外を 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("ライフサイクルメソッドでスローされた例外を LifecycleMethodExecutionExceptionHandler で握りつぶさなかった場合")
        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>");
    }
}
```

```text:実行結果
[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]
    | '-- ライフサイクルメソッドでスローされた例外を LifecycleMethodExecutionExceptionHandler で握りつぶさなかった場合 [X] beforeEach@InnerClass4
    +-- InnerClass3 [OK]
    | '-- ライフサイクルメソッドでスローされた例外を LifecycleMethodExecutionExceptionHandler で握りつぶした場合 [OK]
    +-- InnerClass2 [OK]
    | '-- TestExecutionExceptionHandler はライフサイクルメソッドでスローされた例外に対してどう動くか [X] afterEach@InnerClass2
    '-- InnerClass1 [OK]
      '-- 普通にライフサイクルメソッドで例外がスローされた場合 [X] afterEach@InnerClass1
```

- [LifecycleMethodExecutionExceptionHandler](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.html) を使うと、ライフサイクルメソッドで発生した例外をハンドリングできる
    - 前節の `TestExecutionExceptionHandler` は、あくまでテストメソッド本体で例外が発生した場合しかハンドリングできない
    - `TestExecutionExceptionHandler` は、ライフサイクルメソッドで例外がスローされてもコールバックされない
- `LifecycleMethodExecutionExceptionHandler` には、４つのメソッドが定義されている
    - 各メソッドは、それぞれ `@BeforeAll`, `@BeforeEach`, `@AfterEach`, `@AfterAll` に対応している
    - ライフサイクルメソッドで例外がスローされると、対応するメソッドがコールされる
    - 各メソッドはデフォルトメソッドで定義されており、デフォルトでは受け取った例外をそのままスローしなおすようになっている
- `TestExecutionExceptionHandler` のときと同じで、引数で受け取った例外を再スローしなければ、例外を握りつぶすことができる

## 同じテストを異なるコンテキストで実行する
```java: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 {
    }
}
```

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

```text:実行結果
[supportsTestTemplate] displayName=test1()
[provideTestTemplateInvocationContexts] displayName=test1()
test1()
test1()
test1()
[supportsTestTemplate] displayName=test2()
test3()

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    | +-- [1] [OK]
    | +-- [2] [OK]
    | '-- [3] [OK]
    +-- test2() [X] You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [void sample.junit5.JUnit5Test.test2()]
    '-- test3() [OK]

Failures (1):
  JUnit Jupiter:JUnit5Test:test2()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'test2', methodParameterTypes = '']
    => org.junit.platform.commons.PreconditionViolationException: You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [void sample.junit5.JUnit5Test.test2()]
       ...
```

- [TestTemplateInvocationContextProvider](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html) を使用すると、同じテストメソッドを異なるコンテキストで複数回実行できる
- 異なるコンテキストで実行したいテストは [@TestTemplate](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestTemplate.html) でアノテートしておく必要がある
- `@TestTemplate` が設定されたメソッドごとに `TestTemplateInvocationContextProvider` の `supportsTestTemplate()` がコールされる
    - 対象のテストを処理対象にする（サポートする）場合は `true` を返すように実装する
    - `@TestTemplate` を設定しているのにサポートする `TestTemplateInvocationContextProvider` が１つも存在しない場合は、エラーになる
- サポートするとした場合は、 `provideTestTemplateInvocationContexts()` がコールされる
    - このメソッドは、 [TestTemplateInvocationContext](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html) の `Stream` を返すように実装する
    - `TestTemplateInvocationContext` が、テストを実行するときの１つのコンテキストを表している
    - 複数のコンテキストで実行する場合は、複数の要素を返すように `Stream` を構築する
    - 上記実装例では３つの `MyTestTemplateInvocationContext` を持つ `Stream` を返しているので、 `test1()` メソッドは３回（３つのコンテキストで）実行されている

### コンテキストで表示名を指定する
```java: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 + "]";
        }
    }
}
```

```text:実行結果
.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    | +-- Hoge[1] [OK]
    | +-- Fuga[2] [OK]
    | '-- Piyo[3] [OK]
    :
```

- `TestTemplateInvocationContext` には `getDisplayName()` というメソッドが定義されている
- このメソッドはデフォルトメソッドで定義されていて、デフォルト実装では `"[" + invocationIndex + "]"` を返すようになっている
    - `invocationIndex` は、現在のコンテキストのインデックス（１はじまり）が渡されている
- このメソッドをオーバーライドすることで、任意の表示名を返せるようになる

### コンテキストごとに任意の拡張を追加する
```java: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);
        }
    }
}
```

```text:実行結果
...
beforeEachCallback()
test1()
test1()
afterEachCallback()
...

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    | +-- BeforeEach [OK]
    | '-- AfterEach [OK]
    :
```

- `TestTemplateInvocationContext` には `getAdditionalExtensions()` というメソッドが定義されている
- このメソッドは、そのコンテキストで使用する拡張機能を `List<Extension>` で返す
    - このメソッドもデフォルトメソッドで、デフォルト実装では空の `List<Extension>` を返すようになっている
- 上記実装例では、１つ目のコンテキストでは `BeforeEachCallback` を、２つ目のコンテキストでは `AfterEachCallbak` を設定するように実装している
- `ParameterResolver` を適用すれば、コンテキストごとに異なるパラメータを渡して同じテストメソッドを実行する、といったことが実現できるようになる

## 拡張機能を手続き的に登録する
```java: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()");
    }
}
```

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

```text:実行結果
[classField] beforeAll()
beforeAll()
[classField] beforeEach()
[instanceField] beforeEach()
beforeEach()
test1()
```

- `@ExtendWith` を使った方法の場合、拡張機能クラスの調整は基本的に静的なものになる
    - 拡張機能を実装したクラスのインスタンスは、 Jupiter によって裏で生成される
    - このため、拡張機能クラスのインスタンスに対して細かい調整は基本的にできない
- 一方で [@RegisterExtension](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/RegisterExtension.html) を使用すると、拡張機能クラスの調整を動的に指定できるようになる
- 使用する拡張機能クラスのインスタンスを、拡張機能を使用したいテストクラスのフィールド（`static` or インスタンス）として宣言する
    - このフィールドを `@RegisterExtension` でアノテートすることで、そのフィールドに設定されたインスタンスを拡張機能として登録できる
    - フィールドへのインスタンスの設定は任意のプログラム[^7]で記述できるので、自由に調整したインスタンスを使用できることになる
- `static` で宣言したフィールドを使用した場合は、任意の拡張機能を利用できる
- インスタンスフィールドを使用した場合、 `BeforeAllCallback` のようなクラスレベルの拡張機能や、 `TestInstancePostProcessor` のようなインスタンスレベルの拡張機能は利用できない
    - 実装していても無視される
    - `BeforeEachCallback` のようなメソッドレベルの拡張機能は利用できる

[^7]: 単純にコンストラクタで生成するもよし、ビルダーのような仕組みを用意するもよし、ファクトリメソッドにするもよし

## ServiceLoader を使って自動的に登録する
```text:フォルダ構成
`-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
```

```properties:junit-platform.properties
junit.jupiter.extensions.autodetection.enabled=true
```

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

```java:JUnit5Test
package sample.junit5;

import org.junit.jupiter.api.Test;

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

```text:実行結果
MyServiceLoaderExtension.beforeEach()
test1()
```

- 拡張機能は、 ServiceLoader の仕組みを使って自動的に登録することもできる
    - クラスパス配下に `/META-INF/services/` フォルダを作成し、その中に `org.junit.jupiter.api.extension.Extension` という名前のファイルを作成する
    - ファイルの中には、登録する拡張機能クラスの完全修飾名を記述する
        - 複数登録する場合は改行区切りで列挙する
- ServiceLoader を使った自動登録を有効にするには、設定パラメータ `junit.jupiter.extensions.autodetection.enabled` に `true` を指定する必要がある
    - 設定ファイル(`junit-platform.properties`) で指定する方法があるが、システムプロパティで指定することもできる


## Extension 間でデータを共有する
ある Extension が実行されたときに記録した情報を、別の Extension が実行されたときに参照する方法を考える。

例えば、 `BeforeEachCallback` でテストメソッドの開始時間を記録しておき、 `AfterEachCallback` のときに現在時刻と開始時刻の差から実行時間を出力するようなイメージ。

拡張機能を単一のクラスで作っていれば、手っ取り早くインスタンス変数を使う方法が思い浮かぶ。

```java: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 + ")");
    }
}
```

実際に次のようなテストに適用して実行してみる。

```java: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);
    }
}
```

```text:実行結果
[test1()] time=211 (startTime=1577191073870, endTime=1577191074081)
[test2()] time=400 (startTime=1577191074126, endTime=1577191074526)
[test3()] time=602 (startTime=1577191074529, endTime=1577191075131)
```

それっぽく動いた。

しかし、この実装には**問題がある**。

これのテストを、並列実行してみると問題が顕在化する。

```properties: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
```

※確実に問題を再現させるため、同時並列数を２に固定している

```text:実行結果
[test1()] time=214 (startTime=1577191442987, endTime=1577191443201)
[test3()] time=363 (startTime=1577191443234, endTime=1577191443597)
[test2()] time=402 (startTime=1577191443234, endTime=1577191443636)
```

`test3()` の実行時間が 300ms 程度になってしまっている（実際は 600ms スリープしているので、この値はありえない）。

よく見ると、 `test3()` と `test2()` の `startTime` が同じ値になっていることがわかる。
これはつまり、 `test3()` の時間計測のために使用した `startTime` が、 `test2()` の開始時間になってしまっていることを意味している。

この問題が起こる原因は、 `startTime` の共有を `MyStopwatch` のインスタンス変数で行ってしまっていることにある。
`MyStopwatch` は `JUnit5Test` のテストが実行されている間、同じインスタンスが使用されている。
つまり、各テストメソッドが実行されるときに利用される `MyStopwatch` は、全て同じインスタンスとなっている。

`test3()` が実行されると、 `beforeEach()` で開始時間が `startTime` に記録される。
しかし、直後に `test2()` が並列実行され、 `startTime` の値が `test2()` の開始時間で上書きされてしまっている。
この結果、上述のような問題が発生してしまっている。

このように、 Extension 間でのデータ共有に実装クラスのインスタンス変数を使うと、テストの実行方法次第で予期せぬ問題が起こる可能性がある。
（他にも問題が起こるパターンがあるかもしれないけど、とりあえず思いつくのはこの並列実行のケースくらい）

### Store を使う
この問題を解決するためのものかどうかはわからないが、 `Store` を使うと並列実行されても問題が起らないようにデータ共有を実装できる。

```java: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 + ")");
    }
}
```

```text:実行結果
[test1()] time=213 (startTime=1577193397891, endTime=1577193398104)
[test3()] time=609 (startTime=1577193397891, endTime=1577193398500)
[test2()] time=401 (startTime=1577193398142, endTime=1577193398543)
```

`test3()` の時間が 600ms 程度になって、うまく動いている。
（`startTime` が `test1()` と同じになっているのは、並列数２で `test1()` と `test3()` が同時に開始されたからなので問題ない）

この実装では、 [Store](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Store.html) という仕組みを利用している。
`Store` とは `ExtensionContext` ごとに用意されたデータの入れ物で、任意のデータを Key-Value 形式で保存できるようになっている。

```java
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
store.put("startTime", System.currentTimeMillis());

...

long startTime = store.get("startTime", long.class);
```

`Store` のインスタンスは `ExtensionContext` の [getStore(Namespace)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html#getStore(org.junit.jupiter.api.extension.ExtensionContext.Namespace)) メソッドで取得できる。
引数には [Namespace](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace.html) を指定する。

イメージとしては、 `ExtensionContext` の中には複数の `Store` が格納されており、どの `Store` を取得するかを `Namespace` で指定しているような感じ。
（実際には `Namespace` がキーの一部として利用されているだけで `Store` の実体は単一の `Map` で実装されているけど、イメージとしてはこれでいいと思う）

![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/d8712348-00a8-7eb8-90fb-64e5ffdc3980.jpeg)

このように `Namespace` で `Store` が別れていることで、同じキーを使う拡張機能が複数存在した場合も `Store` を分けてデータを共有できる。
ちなみに、全ての拡張機能でデータを共有したいのであれば、 [Namespace.GLOBAL](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace.html#GLOBAL) という事前定義された定数を使う方法もある。

`Namespace` の生成には [create(Object...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace.html#create(java.lang.Object...)) メソッドを使用する。
引数には任意のオブジェクトを指定できるが、 `equals()` メソッドで比較検証ができるオブジェクトでなければならない。

### Store のライフサイクルとキーの検索方法
Store のライフサイクルは、取得元の `ExtensionContext` と一致している。

```java: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());
    }
}
```

- `beforeAll()` と `beforeEach()` では、まず `Store` の情報を出力してから `hoge` の値をセットしている
- そして、 `afterEach()` と `afterAll()` ではそのまま `Store` の情報を出力している
- それぞれ、 `ExtensionContext` のクラス名についても合わせて出力するようにしている

```java: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 {}
}
```

```text:実行結果
[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
```

- `BeforeAllCallback` や `AfterAllCallback` のようなクラスレベルの Extension では、 `ExtensionContext` として `ClassExtensionContext` が渡されている
- 一方で、 `BeforeEachCallback` や `AfterEachCallback` のようなメソッドレベルの Extension では、 `MethodExtensionContext` が渡されている
- このように `ExtensionContext` は、その拡張機能が実行されているレベルに合わせた実装インスタンスが渡されるようになっている
- これらの `ExtensionContext` には親子関係があり、 `MethodExtensionContext` の親が `ClassExtensionContext` となっている
    - 親のコンテキストをたどっていくと、最終的に `JupiterEngineExtensionContext` へと行き着く
        - 親のコンテキストは、 `ExtensionContext` の [getParent()](https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getParent--) メソッドで取得できる
    - `JupiterEngineExtensionContext` は、一番親（ルート）のコンテキストとなる
        - ルートのコンテキストは、 `ExtensionContext` の [getRoot()](https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getRoot--) メソッドで取得できる
    - なお、 `ExtensionContext` の実装クラスには、他にも `TestTemplateExtensionContext` や `DynamicExtensionContext` というのも存在するが、ここでは割愛
- `Store` は、これらコンテキストのインスタンスごとに保持されている
    - `MethodExtensionContext` は、テストメソッドごとに生成される
    - つまり、 `test1()` と `test2()` の `MethodExtensionContext` は、異なるインスタンスが渡されている
    - したがって、 `Store` もそれぞれ別モノとなる
    - この結果、 `test1()` で `Store` に保存した情報は `test2()` では参照できていない
    - `MyStopwatch` が `Store` を利用することで並列実行時の問題を回避できたのは、このおかげ
        - `BeforeEachCallback` と `AfterEachCallback` はメソッドレベルの Extension
        - 実行されるテストメソッドごとにコンテキストが分かれるので、 `Store` も別々になる
        - このため、並列実行されてもデータが競合せず、正常に動作できるようになっていた
- 親コンテキストの `Store` に保存した情報は、子コンテキストの `Store` からも参照できるようになっている
    - そして、子コンテキスト内で上書きすることもできている
    - しかし、上書きされた情報は子コンテキストのスコープが終了して別のコンテキストに移ると、親コンテキストで設定された元の値に戻っている
        - `test1()` で設定した値が `test2()` の `beforeEach()` や `afterAll()` の段階では元の `"INITIAL VALUE"` に戻っている
    - この動作は、指定されたキーの情報が `Store` に存在しなかった場合に、親の `Store` を再帰的に検索することで実現されている
    - つまり、 `MethodExtensionContext` の `Store` に存在しなかった場合は、親の `ClassExtensionContext` の `Store` を、そこにも無ければルートの `Store` までたどりながら検索するようになっている
    - これにより、 `test1()` からは親コンテキストで設定された値が参照できるようになっている
    - しかし、子コンテキストで `Store` に値を設定しても親コンテキストの `Store` はそのままなので、子コンテキストが終了すると親コンテキストの情報が復活したかのような動作になる
- `ExtensionContext` と `Store` の関係を図で書くと下のような感じになる


![junit5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/fef070b3-d95c-5ca1-02ef-302d7dfdf807.jpeg)

### ライフサイクル終了時に処理を実行する
```java: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);
    }
}
```

```java: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);
    }
}
```

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

```text:実行結果
test1()
  Close Resource > BeforeEach(test1())
test2()
  Close Resource > BeforeEach(test2())
afterAll()
  Close Resource > BeforeAll
```

- [CloseableResource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Store.CloseableResource.html) を実装したインスタンスが `Store` に保存されている場合、その `Store` のライフサイクルが終了するときに [close()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Store.CloseableResource.html#close()) メソッドが自動的にコールされる


# メタアノテーション
```java: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();
    }
}
```

```java: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 {}
```

```java: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);
    }
}
```

```text:実行結果
[test1] testMethodName=test1
[test2] testMethodName=test2
```

- JUnit Jupiter はメタアノテーションの仕組みをサポートしている
- つまり、任意のアノテーションをまとめた別の自作アノテーションを定義できる
- 同じアノテーションの組み合わせを複数箇所で利用しているような場合は、単一の自作アノテーションにまとめて再利用性を高めるようなことができる
- 拡張機能を `@ExtendWith` で `Class` オブジェクトを直接指定するよりも、自作アノテーションを挟むことで具体的な実装から切り離せるというメリットがあるかも

# 設定パラメータ
テストインスタンスのデフォルトライフサイクルを変更する `junit.jupiter.testinstance.lifecycle.default` や、並列実行を有効にするための `junit.jupiter.execution.parallel.enabled` といったテストの挙動を調整するためのパラメータのことを、 **設定パラメータ** と呼ぶ。

設定パラメータを指定する方法には、次の３つが用意されている。

1. Launcher ごとに用意された方法で指定する
2. JVM システムプロパティで指定する
3. `junit-platform.properties` で指定する

１つ目の Launcher ごとに用意されている方法とは、たとえば ConsoleLauncher を使う場合は `--config` オプションで指定する。
Gradle は、現在この方法での指定はサポートされていないらしい（代わりにシステムプロパティか `junit-platform.properties` を使った方法を採用する）。
Maven の場合は、 `configurationParameters` プロパティで指定する。

２つ目の JVM システムプロパティは、そのままシステムプロパティで指定する（指定方法は、各 Launcher によって異なる）。

３つ目は、 `junit-platform.properties` というプロパティファイルをクラスパスのルートに配置することで有効になる。

同じキーの設定パラメータがこれらの異なる方法で指定されていた場合、上の方で定義された値のほうが優先される。
つまり、 Launcher ごとに用意された値の優先度が最も高くなり、 `junit-platform.properties` で定義された値の優先度は最も低くなる。

実際に、 ConsoleLauncher で試してみる。

```java: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>"));
    }
}
```

- 設定パラメータの値は `ExtensionContext` の `getConfigurationParameter()` で参照できる
- ここでは、 `hoge`, `fuga`, `piyo` の３つの設定パラメータの値を出力してみている

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

```properties:junit-platform.properties
hoge=HOGE@properties
fuga=FUGA@properties
piyo=PIYO@properties
```

- `hoge`, `fuga`, `piyo` すべてを定義している

```console
> java -Dfuga=FUGA@SystemProperty ^
       -Dpiyo=PIYO@SystemProperty ^
       -jar junit-platform-console-standalone-1.5.2.jar ^
       --config piyo=PIYO@ConfigOption ^
...
```

- `fuga` と `piyo` を JVM のシステムプロパティで指定している
- `piyo` だけは `--config` オプションでも指定している

```text:実行結果
key=hoge, value=HOGE@properties
key=fuga, value=FUGA@SystemProperty
key=piyo, value=PIYO@ConfigOption
test1()
```

各キーと、それぞれの設定方法ごとに指定していた値の関係を表にすると、下のようになる。
（`*` が付いているのが、最終的に採用された値）

|キー|junit-platform.properties|JVMシステムプロパティ|--config|
|----|-------------------------|-------------------|--------|
|`hoge`|`HOGE@properties` *    |                   |        |
|`fuga`|`FUGA@properties`      |`FUGA@SystemProperty` *|    |
|`piyo`|`PIYO@properties`      |`PIYO@SystemProperty`  |`PIYO@ConfigOption` *|

`--config` で指定した値の方が優先され、 `junit-platform.properties` で指定した値の優先度が低いことがわかる。


# アサーション
JUnit5 には、 [Assertions](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html) というアサーション用のクラスが用意されている。

ここには、 [assertEquals()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertEquals(java.lang.Object,java.lang.Object)) などの基本的なアサーションメソッドが用意されている。
また、 [assertTimeout()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertTimeout(java.time.Duration,org.junit.jupiter.api.function.Executable)) や、 [assertAll()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertAll(org.junit.jupiter.api.function.Executable...)) のような、ちょっと便利そうなメソッドも用意されている。

ただ、あくまで必要最低限程度のものしか用意されていない印象。

JUnit5 は、任意のサードパーティのアサーションライブラリを使用できるようになっている。
つまり、 [AssertJ](http://joel-costigliola.github.io/assertj/) や [Hamcrest](http://hamcrest.org/JavaHamcrest/) などを依存ライブラリに追加すれば、 JUnit4 のときのように普通に使うことができる。

以下は、 AssertJ を依存関係に追加した場合の例になる。

```gradle:build.gradle
dependencies {
    ...
    testImplementation "org.assertj:assertj-core:3.11.1"
}
```

```java: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);
    }
}
```

```text:実行結果
...
.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- test() [X]
          Expecting:
           <10>
          to be equal to:
           <8>
          but was not.
...
```

実際は、これらサードパーティのアサーションを使うことになると思うので、標準のアサーションの使い方については割愛。


# 参考
- [JUnit 5 ユーザーガイド](https://oohira.github.io/junit5-doc-jp/user-guide/)
- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)
- [Testing in Java & JVM projects](https://docs.gradle.org/current/userguide/java_testing.html#using_junit5)