JUnit5の使い方

この記事は[MDC Advent Calendar 2020 19日目](https://qiita.com/advent-calendar/2020/mdc)の記事です。

# はじめに

皆さん、単体テスト書いてますか？
（私自身も最近あまり書いてないですが...）

- 実装はできるのにテストコードがあまり書けない
- JUnit4までは書いたことあるけど、JUnit5はあんまり...

意外と多いそんな人たち向けに、JUnit5でテストコードを書き始めるためのお助けになればと思います。

# 概要

## JUnit5とは

- 言わずと知れたJava開発における最もメジャーなテストフレームワークです
- これまでのバージョンのJUnitとは異なり、JUnit5は3つのサブプロジェクトに含まれる複数のモジュールで構成されます  
**JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage**

  - JUnit Platform
     - JVM上でテストフレームワークを起動するための基盤となる
     - 上記のプラットフォーム上で動作するテストフレームワークを開発するための`TestEngine`APIを定義している
     - コマンドラインからプラットフォームを起動するための`Console Launcher`や、 GradleやMaven用のビルドプラグイン、JUnit4ベースのテストランナーなどを提供し、あらゆる`TestEngine`を実行できるようになっている
  - JUnit Jupiter
     - JUnit5でテストを書くために最低限必要な依存関係をまとめたもの
  - JUnit Vintage
    - JUnit3またはJUnit4ベースのテストを実行するための`TestEngine`を提供する

- 長くなりましたが、とりあえずJUnit5でテストを書き始めたいなら、**JUnit Jupiter**を依存関係に追加しとけばOKです（以下はgradleのサンプル）

```build.gradle
plugins {
    id "java"
}

sourceCompatibility = 8
targetCompatibility = 8
[compileJava, compileTestJava]*.options*.encoding = "UTF-8"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.7.0"
}
```

## サポートされるJavaバージョン

- JUnit5を実行するためには、Java8以上が必要です

# テストの書き方

## テストメソッド

```java
package sample.junit5;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class JUnit5Test {

    @Test
    void success() {
        assertEquals(12, 12);
    }

    @Test
    void failure() {
        assertEquals(5, 12);
    }
}
```

|アノテーション|説明|
|---|---|
|`@Test`|付与されたメソッドがテストメソッドになる|

- アサーションメソッド（例は`assertEquals(expected, actual)`）を使い、期待値と実測値を比較する
  - 実際には、テスト対象メソッドを呼び出して、その戻り値と期待値を比較します
- JUnit5では、テストクラス・テストメソッドともに`public`である必要がなくなりました

## 前処理・後処理

```java
package sample.junit5;

import org.junit.jupiter.api.*;

class JUnit5Test {

    @BeforeAll
    static void beforeAll() {
        System.out.println("☆ beforeAll()");
    }

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

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

    @AfterAll
    static void afterAll() {
        System.out.println("★ afterAll()");
    }

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

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

```:実行結果
☆ beforeAll()
☆☆ beforeEach()
~~~ test1() ~~~
★★ afterEach()
☆☆ beforeEach()
~~~ test2() ~~~
★★ afterEach()
★ afterAll()
```

|アノテーション|説明|
|---|---|
|`@BeforeAll`|付与されたメソッドは、1番最初に１度だけ実行される<br>メソッドはstaticである必要がある|
|`@BeforeEach`|付与されたメソッドは、各テストメソッドの前に毎回実行される|
|`@AfterAll`|付与されたメソッドは、1番最後に１度だけ実行される<br>メソッドはstaticである必要がある|
|`@AfterEach`|付与されたメソッドは、各テストメソッドの後に毎回実行される|

## テストグルーピング

```java
package sample.junit5;

import org.junit.jupiter.api.*;

class JUnit5Test {

    @Test
    void test1() {...}

    @Nested
    class group1 {
        @Test
        void test2() {...}

        @Test
        void test3() {...}
    }
}
```

|アノテーション|説明|
|---|---|
|`@Nested`|非staticなクラスに付与するとテストクラスを入れ子にできる|

- テストクラスはそれなりのステップ数になることも多いので、テスト観点ごとにグルーピングするのがおすすめです

## パラメータテスト

```java
package sample.junit5;

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

class JUnit5Test {

    @ParameterizedTest
    @ValueSource(strings = {"foo", "bar", "baz"})
    void test(String value) {
        System.out.println("VALUE: " + value);
    }
}
```

```:実行結果
VALUE: foo
VALUE: bar
VALUE: baz
```

|アノテーション|説明|
|---|---|
|`@ParameterizedTest`|付与されたメソッドは、パラメータテストになる|
|`@ValueSource`|リテラル値の配列を1つ指定することができ、パラメータテスト呼び出しで１つのパラメータを提供する（例はString）|

- パラメータの分だけテストが実行されるようなイメージです
- 以下のリテラル値が提供されています
  - `short`
  - `byte`
  - `int`
  - `long`
  - `float`
  - `double`
  - `char`
  - `java.lang.String`
  - `java.lang.Class`

# 最後に

- すいません、時間がなさすぎて書きたいことの半分も書けてない状態です（...12/18 23時過ぎに書き始めました）
- とにかく言いたいことは、JUnit5は便利で簡単です
- 皆さん、テストコードはちゃんと書きましょう

# 参考

- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)