JUnit JupiterのExtensionのライフサイクルを調べてみた

JUnit 5(JUnit Jupiter)のExtensionを書こうとしてExtensionインスタンスのライフサイクルってどうなってるんだっけ？となったので備忘録的にメモ。

## 環境
- AdoptOpenJDK 11
- JUnit 5.5.0

## TL;TR

- `@Extension`で登録したインスタンスはテストクラスごとに共有され、テストクラスが異なればExtensionインスタンスも異なる。
- `@RegisterExtension`で登録したインスタンス
    - staticフィールドの場合はテストクラスごとに共有される。
    - non-staticフィールドの場合はテストクラスのライフサイクルに依存する。

## Extension Modelのおさらい

JUnit 5のExtension Modelは各テストに対する前処理・後処理・例外処理などを共通化するための仕組み。Junit 4の`Rule`などに比べて自由度は下がるが、拡張ポイントに対応したインタフェースを実装するだけで手軽にExtensionを作成できる。

Extensionを使うには3つの方法が用意されている。

1. `@Extension`でExtensionを登録する。
2. `@RegisterExtension`でプログラマティックにExtensionを登録する。
3. ServiceLoaderで自動的に登録する。

今回は1と2の方法でそれぞれExtensionインスタンスの生成がどのタイミングで行われるかを確認する。

### 確認に用いるExtensionは以下の通り。

```java:FooExtension.java
import org.junit.jupiter.api.extension.*;

public class FooExtension implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback {

    {
        System.out.println(String.format("[%s] created", this.toString()));
    }

    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] before all", this.toString()));
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println(String.format("[%s] before each", this.toString()));
    }

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] after each", this.toString()));
    }

    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] after all", this.toString()));
    }
}
```

## `@Extension`でExtensionを登録する

### ソース

```java
@ExtendWith(FooExtension.class)
public class ExtensionTest {

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

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

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

### 結果
```
[example.FooExtension@70325e14] created
[example.FooExtension@70325e14] before all
[example.FooExtension@70325e14] before each
test 1
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] before each
test 2
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] before each
test 3
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] after all
```

各テストで１つExtensionのインタンスが共有されていることがわかります。
なお、ExtensionTestはテストごとにインスタンスが生成されますが、`@TestInstance(Lifecycle.PER_CLASS)`をつけてExtensionTestのインスタンスも共有するように変更した場合も同じ結果になりました。

## `@RegisterExtension` + staticフィールドでExtensionを登録する

### ソース

```java:SecondExtensionTest.java
public class SecondExtensionTest {

    @RegisterExtension
    static FooExtension fooExtension = new FooExtension();

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

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

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

### 結果

```
[example.FooExtension@55183b20] created
[example.FooExtension@55183b20] before all
[example.FooExtension@55183b20] before each
test 1
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] before each
test 2
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] before each
test 3
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] after all
```

当然ですが、staticフィールドなので同じインスタンスが共有されます。

## `@RegisterExtension` + non-staticフィールドでExtensionを登録する

先程のSecondExtensionTestの`fooExtension`の`static`を外してみます。

### `Lifecycle.PER_METHOD`の場合

#### ソース

```diff:SecondExtensionTest.java
public class SecondExtensionTest {

    @RegisterExtension
-   static FooExtension fooExtension = new FooExtension();
+   FooExtension fooExtension = new FooExtension();

    //後略
}
```

#### 結果

```
[example.FooExtension@40a4337a] created
[example.FooExtension@40a4337a] before each
test 1
[example.FooExtension@40a4337a] after each
[example.FooExtension@fa4c865] created
[example.FooExtension@fa4c865] before each
test 2
[example.FooExtension@fa4c865] after each
[example.FooExtension@3bd82cf5] created
[example.FooExtension@3bd82cf5] before each
test 3
[example.FooExtension@3bd82cf5] after each
```

テストごとにExtensionのインスタンスが異なっていることが分かります。これはSecondExtensionTest自体のインスタンスがテストごとに毎回生成されていることが原因です。
なお、BeforeAllとAfterAllが動いていませんが、メソッドに`@BeforeAll`をつけるときと同様`Lifecycle.PER_METHOD`の場合はstaticにしなければいけません。

### `Lifecycle.PER_CLASS`の場合

ということでSecondExtensionTestのライフサイクルを、１回だけインスタンスが生成されるかたちにしてみます。

#### ソース

```diff:SecondExtensionTest.java
+ @TestInstance(Lifecycle.PER_CLASS)
public class SecondExtensionTest {

    @RegisterExtension
    FooExtension fooExtension = new FooExtension();

    //後略
}
```

#### 結果

```
[example.FooExtension@c730b35] created
[example.FooExtension@c730b35] before all
[example.FooExtension@c730b35] before each
test 1
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] before each
test 2
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] before each
test 3
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] after all
```

Extensionインスタンスが共有されています。また、BeforeAllとAfterAllが動くようになりました。このようにして、non-staticフィールドでExtensionを登録した場合は、当然ながら元のテストクラスのライフサイクルに依存するわけです。

## ちゃんと読めば書いてある

https://junit.org/junit5/docs/current/user-guide/#extensions