JUnit 5 で全 @BeforeAll の前に処理を行う

全テストの実行前に static 変数のフラグをセットしたかったため、そうしたことのできる方法を探したところ、JUnit 5 と IntelliJ や Maven Surefire Plugin の組み合わせで実現可能だとわかったためご紹介します。

JUnit 5 User Guide の [6.4.5. Registering a LauncherSessionListener](https://junit.org/junit5/docs/current/user-guide/#launcher-api-launcher-session-listeners-custom) を読めばわかることではあるのですが、もっと簡単な例と実行結果が載っている日本語の記事があるといいんじゃないかと思って執筆しました。


## 前提

- Gradle 4.6 以上
- Maven Surefire/Failsafe 3.0.0-M6 以上
- IntelliJ IDEA 2017.3 以上

Maven では Surefire/Failsafe Plugin のプレリリース版を使っている点にご注意ください。


## 実現方法

Maven でプロジェクトを作成しました。

src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener に LauncherSessionListener を実装したクラスの完全修飾クラス名を記載します。

LauncherSessionListener を実装したクラスでは、`launcherSessionOpened` メソッドをオーバーライドし、その中にやりたい処理を記述します。

### ディレクトリー構成とファイル内容の例

```plaintext:ディレクトリー構成
.
├── pom.xml
└── src
    └── test
        ├── java
        │   ├── ATest.java
        │   ├── BTest.java
        │   ├── MyIT.java
        │   └── org
        │       └── example
        │           └── MySessionListener.java
        └── resources
            └── META-INF
                └── services
                    └── org.junit.platform.launcher.LauncherSessionListener
```

pom.xml は以下のようにしました。

- Surefire Plugin を使って test フェイズに Test*、*Test、*Tests、*TestCase クラスを対象に単体テストを実行します。
- Failsafe Plugin を使って verify フェイズに IT*、*IT、*ITCase クラスを対象に結合テストを実行します。IT は Integration Test の略で結合テスト (もしくは統合テスト) のことです。
  - `goals` 内に `integration-test` や `verify` と書く必要があります。さっき忘れてました💦
- Java 17 を使っていますが、もっと古くても大丈夫なはずです。
- 冒頭の `artifactId` の `junit-platform-launcher-session-listener` となっている箇所は、`my-example-project` など適当な内容で大丈夫です。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>junit-platform-launcher-session-listener</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <version>1.9.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M8</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>3.0.0-M8</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
```

1行だけの org.junit.platform.launcher.LauncherSessionListener です。

```plaintext:src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
org.example.MySessionListener
```

以下の LauncherSessionListener 実装クラスでは、セッション開始時に「はじまりはじまり〜」、終了時に「おしまい」とコンソール出力するようにしました。

```java:src/test/java/org/example/MySessionListener.java
package org.example;

import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;

public class MySessionListener implements LauncherSessionListener {
    @Override
    public void launcherSessionOpened(LauncherSession session) {
        System.out.println("はじまりはじまり〜");
    }

    @Override
    public void launcherSessionClosed(LauncherSession session) {
        System.out.println("おしまい");
    }
}
```

そして、`@BeforeAll` と `@AfterAll` のある単体テストクラスを2つ、結合テストクラスを1つ作成しました。ただの例なのでアサーションはありません。

```java:src/test/java/ATest.java
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class ATest {
    @BeforeAll
    static void beforeAll() {
        System.out.println("beforeAll at ATest");
    }

    @Test
    void a() {
        System.out.println("method A");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("afterAll at ATest");
    }
}
```

```java:src/test/java/BTest.java
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class BTest {
    @BeforeAll
    static void beforeAll() {
        System.out.println("beforeAll at BTest");
    }

    @Test
    void b() {
        System.out.println("method B");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("afterAll at BTest");
    }
}
```

```java:src/test/java/MyIT.java
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class MyIT {
    @BeforeAll
    static void beforeAll() {
        System.out.println("beforeAll at MyIT");
    }

    @Test
    void myIT() throws InterruptedException {
        System.out.println("executing MyIT...");
        Thread.sleep(10_000);
        System.out.println("MyIT end");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("afterAll at MyIT");
    }
}
```

## 実行例

### Maven

`mvn verify` を実行すると、Surefire および Failsafe Plugin により以下のような出力が得られます。

```
[INFO] Scanning for projects...
[INFO] 
[INFO] --------< org.example:junit-platform-launcher-session-listener >--------
[INFO] Building junit-platform-launcher-session-listener 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ junit-platform-launcher-session-listener ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/hiro/IdeaProjects/junit-platform-launcher-session-listener/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ junit-platform-launcher-session-listener ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ junit-platform-launcher-session-listener ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ junit-platform-launcher-session-listener ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:3.0.0-M8:test (default-test) @ junit-platform-launcher-session-listener ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
はじまりはじまり〜
[INFO] Running BTest
beforeAll at BTest
method B
afterAll at BTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.016 s - in BTest
[INFO] Running ATest
beforeAll at ATest
method A
afterAll at ATest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s - in ATest
おしまい
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ junit-platform-launcher-session-listener ---
[INFO] Building jar: /Users/hiro/IdeaProjects/junit-platform-launcher-session-listener/target/junit-platform-launcher-session-listener-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- maven-failsafe-plugin:3.0.0-M8:integration-test (default) @ junit-platform-launcher-session-listener ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
はじまりはじまり〜
[INFO] Running MyIT
beforeAll at MyIT
executing MyIT...
MyIT end
afterAll at MyIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.036 s - in MyIT
おしまい
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-failsafe-plugin:3.0.0-M8:verify (default) @ junit-platform-launcher-session-listener ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.263 s
[INFO] Finished at: 2023-02-14T01:28:55+09:00
[INFO] ------------------------------------------------------------------------
```

単体テストの開始時と終了時、結合テストの開始時と終了時にそれぞれメッセージが出力されます。

Surefire Plugin で単体テストだけ実行したい場合には `mvn test` を実行してください。

### IntelliJ IDEA 2022.3.2

#### IntelliJ のコンソール出力

Maven プロジェクトとして作成し、Run 'All Tests' で実行しました。

```
はじまりはじまり〜
beforeAll at MyIT
executing MyIT...
MyIT end
afterAll at MyIT
beforeAll at ATest
method A
afterAll at ATest
beforeAll at BTest
method B
afterAll at BTest
おしまい

Process finished with exit code 0
```

テスト セッションの開始時に「はじまりはじまり〜」、終了時に「おしまい」と表示できています。Maven と違って、IT も含めて全件実行されるんですね。

IT を除外したい場合は、Run configurations の Pattern に `^(?!.*IT.*).*$` と書くと良いようです（ちょっと冗長な気も…）。先読みや後読みの正規表現は苦手なんですが、Java の `String::matches` で実験してみた限りでは、`(?!IT).*(?<!IT(Case)?)` で Failsafe Plugin の対象を除外できそうです。

https://www.jetbrains.com/help/idea/2022.3/run-debug-configuration-junit.html#required

Gradle プロジェクトの場合には、Gradle と同じクラスが対象になるようです。

https://qiita.com/opengl-8080/items/efe54204e25f615e322f#intellij-idea-%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AB%E5%AF%BE%E8%B1%A1%E3%81%A8%E3%81%AA%E3%82%8B%E3%82%AF%E3%83%A9%E3%82%B9

#### 画面 (Beta 版の New UI 利用)

![Screenshot 2023-02-14 at 1.40.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782342/9beb6038-6d95-6deb-896c-ae5f453c5c3b.png)

---

簡単ですが、ご紹介できる内容は以上です。

繰り返しになりますが、詳細は JUnit 5 User Guide の [6.4.5. Registering a LauncherSessionListener](https://junit.org/junit5/docs/current/user-guide/#launcher-api-launcher-session-listeners-custom) をご覧ください。