JUnit4とJUnit5の違い

## はじめに
JUnitについて調査をしていた時、JUnit5以前の情報も多く目に入り、JUnit5との記法の違いから少し戸惑う、ということがありました。
この記事は、そうした調査の過程で個人的に気に留めておきたいと思ったJUnit4とJUnit5のいくつかの違いについて、メモしたものになります。

## 実行環境の違い
- JUnit4 : Java 5.0以上。これにより、JUnit4からはアノテーションを使用した記法が導入されました。
- JUnit5 : Java 8以上。これにより、JUnit5からはラムダ式を使用した記法も使用できるようになりました。

JUnit3以前はアノテーションが無い世代のJavaが対象だったため、メソッドの命名規則でテストメソッドの判別などを行なっていたようです。

## クラスパスの違い
- JUnit4のクラスはorg.junitパッケージを使用します。
- JUnit5のクラスはorg.JUnit.jupiterパッケージを使用します。

MavenやGradleで指定するライブラリも異なりますし、importの記述も変わります。
（後方互換性を保つためでしょうか？）

## アノテーションの違い
JUnit4からJUnit5になり、いくつかのアノテーションが変更になりました。

| JUnit4 | JUnit5 | 内容 |
|:-----------|:------------|:------------|
| <span>@</span>Before       | <span>@</span>BeforeEach        | テスト実行前の処理         |
| <span>@</span>After       | <span>@</span>AfterEach        | テスト実行後の処理         |
| <span>@</span>BeforeClass       | <span>@</span>BeforeAll        | テストクラスのテスト実行前に一度だけ行う処理         |
| <span>@</span>AfterClass       | <span>@</span>AfterAll        | テストクラスのテスト実行後に一度だけ行う処理         |
| <span>@</span>Ignore       | <span>@</span>Disable        | テスト実行からの除外         |
| <span>@</span>Category       | <span>@</span>Tag        | テストケースをカテゴライズ |
テストメソッドを示すためのアノテーション<span>@</span>Testについては宣言自体の変更はありませんが、JUnit4では（後述する）検出する例外や、タイムアウトの値などを指定する際に、そのパラメータの指定などができました。JUnit5ではそれらは別アノテーションにより別途指定する形となり、<span>@</span>Testアノテーションは、ただテストメソッドである事を指定するだけのものとなったようです。

## アサーション
- JUnit4のアサーションはorg.junit.Assertクラスのstaticメソッドとして提供されています。 基本的にassertThatメソッドとorg.hamcrest.Matcherを使用したアサーションを行うのが一般的なようです。
- JUnit5のアサーションはorg.junit.jupiter.api.Assertionsクラスのstaticメソッドとして提供されています。 基本的な使い方はJUnit4と変わらないようですが、Lambda式に対応するようになっています。
JUnit4で導入されたassertThatメソッドは、このクラス内には存在しません。
 
## 一部機能の実装の違い
JUnit4では、特殊なテストケースを実行するために、拡張機能として実装されたテストランナーを<span>@</span>RunWIthアノテーションでクラスに指定して実行していました。
JUnit5ではテストランナーの指定が不要になり、そのあたりの記法もシンプルになっています。

### 検出する例外の定義
JUnit4では<span>@</span>Testアノテーションのexpected属性に検出したい例外を指定します。

```java
@Test(expected = NullPointerException.class)
public void 例外送出テスト() throws Exception {
    // do something
}
```

JUnit5ではAssertions.assertThrowsを使用して処理をします。

```java
@Test
void 例外送出テスト() throws Exception {
    Assertions.assertThrows(NullPointerException.class, () -> {
        // do something
    });
}
```

### タイムアウト値の定義
JUnit4では<span>@</span>Testアノテーションのtimeout属性にテストのタイムアウト値を指定します。

```java
@Test(timeout = 10L)
public void タイムアウトテスト() throws Exception {
    // do something
}
```

JUnit5ではAssertions.assertTimeoutを使用して処理をします。

```java
@Test
void タイムアウトテスト() throws InterruptedException {
    Assertions.assertTimeout(Duration.ofMillis(10), () -> {
        // do something
    });
}
```

### 構造化されたテストケースの実行
JUnit4では、構造化されたテストケースの実行にはorg.junit.experimental.runners.Enclosedクラスをテストランナーに指定して実行します。
<span>@</span>RunWIthアノテーションにEnclosed.classを指定する事で、ネストしたテストクラスを複数定義できるようになります。

```java
@RunWith(Enclosed.class)
public class SampleTest {

    public static class テストパターン1 {
        @Before
        public void setUp() throws Exception {
            // テストパターン1の前処理
        }

        @After
        public void tearDown() throws Exception {
            // テストパターン1の後処理
        }

        @Test
        public void テスト1() throws Exception {
            // do something
        }
    }

    public static class テストパターン2 {
        @Before
        public void setUp() throws Exception {
            // テストパターン2の前処理
        }

        @After
        public void tearDown() throws Exception {
            // テストパターン2の後処理
        }

        @Test
        public void テスト2() throws Exception {
            // do something
        }
    }
}
```

JUnit5では<span>@</span>Nestedアノテーションを内部テストクラスに設定します。

```java
class SampleTest {

    @Nested
    class テストパターン1 {
        @BeforeEach
        void setUp() throws Exception {
            // テストパターン1の前処理
        }

        @AfterEach
        void tearDown() throws Exception {
            // テストパターン1の後処理
        }

        @Test
        void テスト1() throws Exception {
            // do something
        }
    }

    @Nested
    class テストパターン2 {
        @BeforeEach
        void setUp() throws Exception {
            // テストパターン2の前処理
        }

        @AfterEach
        void tearDown() throws Exception {
            // テストパターン2の後処理
        }

        @Test
        void テスト2() throws Exception {
            // do something
        }
    }
}
```

### パラメータ化テスト
JUnit4では、テストケースとテストデータを分離し、同じテストメソッドを複数のパラメータでテストしたい場合、org.junit.experimental.theories.Theoriesクラスをテストランナーに指定して実行します。
このクラスランナーを指定した場合は、テストメソッドに<span>@</span>Testの代わりに<span>@</span>Theoryを指定します。
また、テストメソッドに渡すパラメータは<span>@</span>DataPointsアノテーションの付いたstaticフィールドなどで定義します。

```java
@RunWith(Theories.class)
public class TheoriesTest {
    @DataPoints
    public static String[] keywords = new String[] {
        "foo", "bar", "baz"
    }

    @Theory
    public void 文字列テスト(String keyword) throws Exception {
        // do something
    }
}
```

JUnit5では、<span>@</span>Testの代わりに<span>@</span>ParameterizedTestを指定することで同様のテストが行えます。

```java
@ParameterizedTest
@ValueSource(strings = { "foo", "bar", "baz" })
void 文字列テスト(String keyword) throws Exception {
    // do something
}
```

## おわりに
JUnit4のサンプルコードは（構造などは別として）JUnit5のテストコードを書くのに、記法が大きく変わっていて、あまり参考にはできないという事が、いろいろな記事を参照していてわかりました。

JUnit4には良い書籍が出ているので、JUnit5にも同様の書籍（または改訂版）が出ることを期待しています。