概要

テストコードは書けるけれど「何をどこまでテストすべきか」で毎回迷う、JUnit 4 から 5 への移行で何が変わったのか整理できていない――そんな声は現場で少なくありません。JUnit 5 は Jupiter エンジンの導入により、アノテーション体系が刷新され、@DisplayName による日本語テスト名、@Nested によるグループ化、@ParameterizedTest による表形式テストなど、実務で使いやすい機能が揃いました。この記事では、Calculator クラスを題材に、基本アノテーションの使い方、例外テスト(assertThrows)、複数アサーションの一括検証(assertAll)、パラメータ化テストまでを一通り整理します。テスト設計のコツとして Given-When-Then パターンにも触れ、読みやすく保守しやすいテストコードを書くための実践的な指針を示します。

使いどころ

新規開発のビジネスロジック(消費税計算、割合計算など)に対し、JUnit 5 のパラメータ化テストで境界値を網羅する

既存の JUnit 4 テストを JUnit 5 に移行し、@Nested と @DisplayName でテストの構造と可読性を改善する

例外が発生すべきケース(0除算、null 入力、範囲外の値)を assertThrows で明示的に検証し、回帰テストとして保護する

複数条件をまとめて確認したいサービスクラスで assertAll を使い、失敗を一度に洗い出す

レビューしやすいテスト命名に揃えるため、@DisplayName で業務用語ベースの表示名を整備する

コード例

CalculatorTest.java
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    // テスト対象: record で不変な Calculator を定義
    record Calculator() {
        int add(int a, int b) { return a + b; }
        int subtract(int a, int b) { return a - b; }
        int multiply(int a, int b) { return a * b; }
        int divide(int a, int b) {
            if (b == 0) {
                throw new ArithmeticException("0除算はできません");
            }
            return a / b;
        }
    }

    private Calculator calc;

    @BeforeEach
    void setUp() {
        calc = new Calculator();
    }

    @Nested
    @DisplayName("加算テスト")
    class AddTests {
        @Test
        @DisplayName("正の数の加算")
        void addPositive() {
            assertEquals(8, calc.add(3, 5));
        }

        @Test
        @DisplayName("負の数を含む加算")
        void addNegative() {
            assertEquals(-2, calc.add(-5, 3));
        }

        @ParameterizedTest
        @CsvSource({ "1,2,3", "0,0,0", "-1,1,0", "100,200,300" })
        @DisplayName("様々な値の加算テスト")
        void addParameterized(int a, int b, int expected) {
            assertEquals(expected, calc.add(a, b));
        }
    }

    @Nested
    @DisplayName("除算テスト")
    class DivideTests {
        @Test
        @DisplayName("正常な除算")
        void divideNormal() {
            assertEquals(5, calc.divide(10, 2));
        }

        @Test
        @DisplayName("ゼロ除算で ArithmeticException が発生する")
        void divideByZero() {
            assertThrows(ArithmeticException.class,
                () -> calc.divide(10, 0));
        }
    }

    @Test
    @DisplayName("複数のアサーションをまとめて検証")
    void multipleAssertions() {
        assertAll("四則演算の検証",
            () -> assertEquals(8,  calc.add(3, 5)),
            () -> assertEquals(-2, calc.subtract(3, 5)),
            () -> assertEquals(15, calc.multiply(3, 5)),
            () -> assertEquals(2,  calc.divide(10, 5))
        );
    }

    @AfterEach
    void tearDown() {
        calc = null;
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record でテスト対象の Calculator を不変に定義できる。@Nested でテストをグループ化し、@DisplayName で日本語テスト名を付けると構造が明確になる。

Java 17
// Java 17: record + @Nested でテストを構造化
record Calculator() {
    int add(int a, int b) { return a + b; }
    int divide(int a, int b) {
        if (b == 0) throw new ArithmeticException("0除算");
        return a / b;
    }
}
@Nested @DisplayName("加算テスト")
class AddTests {
    @ParameterizedTest
    @CsvSource({"1,2,3", "0,0,0", "-1,1,0"})
    void add(int a, int b, int expected) {
        assertEquals(expected, calc.add(a, b));
    }
}

Library Comparison

JUnit 5(Jupiter)Java のテストフレームワークの標準。@Nested、@ParameterizedTest、@DisplayName など実務向け機能が充実。新規プロジェクトでは第一選択。assertThat スタイルのアサーションは標準では提供されない。流暢なアサーションが必要なら AssertJ を併用する。
AssertJassertThat(result).isEqualTo(expected) のような流暢なアサーションで可読性を上げたいとき。JUnit 5 と併用が前提。依存追加が必要。assertEquals で十分なプロジェクトには過剰。チーム内でスタイルが混在するリスクがある。
TestNGデータプロバイダーやテストグループなど、大規模テストスイートの管理機能が必要なとき。JUnit 5 の @ParameterizedTest や @Nested で多くのユースケースをカバーできるため、新規採用のメリットは薄れている。

注意点

@Test は org.junit.jupiter.api.Test をインポートすること。org.junit.Test(JUnit 4)をインポートすると実行されない

@BeforeEach は各テストメソッドの前に毎回実行される。テスト間で状態を共有したい場合は @BeforeAll(static メソッド)を使うが、テストの独立性を損なうため慎重に使う

@ParameterizedTest と @Test を同じメソッドに付けると二重実行される。パラメータ化テストには @ParameterizedTest だけを付ける

assertAll に渡すラムダが1つでも失敗すると、残りも実行されたうえで全失敗が報告される。assertEquals を個別に書く場合は最初の失敗で中断されるため、挙動が異なる

@Nested クラスは内部クラス(non-static inner class)でなければならない。static をつけるとテストが認識されない

例外テストでは例外型だけでなくメッセージや原因も必要に応じて確認する。型だけだと別原因の失敗を見逃しやすい

1メソッドで複数の振る舞いを検証しすぎると失敗理由が読み取りにくい。正常系と異常系は分けて書く方が保守しやすい

FAQ

JUnit 4 から JUnit 5 への移行で最低限変えるべき点は何ですか。

import を org.junit.jupiter.api に変更し、@Before を @BeforeEach に、@Rule を @ExtendWith に置き換えます。junit-vintage-engine を入れれば JUnit 4 テストも並行実行できます。

@ParameterizedTest のデータソースは @CsvSource 以外に何がありますか。

@ValueSource(単一値)、@EnumSource(enum 全値)、@MethodSource(メソッド参照)が実務でよく使われます。複雑なデータには @MethodSource が柔軟です。

テストメソッド名は日本語にしてもよいですか。

技術的には問題ありませんが、@DisplayName に日本語を書き、メソッド名は英語にするのがチームでの運用上は無難です。CI のログでも文字化けしにくくなります。

assertAll はどんな場面で使うと効果的ですか。

同じ入力に対する複数の戻り値や DTO の各フィールドをまとめて確認したい場面で有効です。失敗を一度に確認できる反面、 unrelated な検証を1つに詰め込みすぎないようにします。

@Nested は必ず使うべきですか。

必須ではありません。テスト対象の状態や操作ごとにまとまりがあり、クラス分割すると読みやすくなるときに使うのが自然です。小さなテストクラスでは無理に導入しなくて構いません。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます