JUnit5テスト設計:タグ・フィクスチャー・パラレル実行まで徹底ガイド
1. カテゴリ化(@Tag)と構造化(@Nested)
目的:重いテストと軽いテストを分けて、ビルドやCIでの実行制御を可能にする。
1-1. @Tag の使い方
@Tag("integration") @Test void integrationTest() { … }
Gradle 設定例:
test { useJUnitPlatform { includeTags 'unit', 'fast' excludeTags 'integration', 'slow' } }
CLI での切り替え:
./gradlew test -DincludeTags=unit ./gradlew test -DexcludeTags=integration
1-2. @Nested の使い方
@Nested @Tag("validation") class ValidationTests { @Test void whenNameEmpty_thenError() { … } }
ネストクラス個別実行:
./gradlew test --tests 'com.example.MyTestClass$ValidationTests*'
2. 共通フィクスチャー(セットアップ/後処理)
目的:重複コードを減らし、リソース初期化・解放を一元管理する。
@TestInstance(TestInstance.Lifecycle.PER_CLASS) class MyServiceTest { private InMemoryDatabase db; private ApiClient client; @BeforeAll void initAll() { db = new InMemoryDatabase(); // 一度だけ初期化 client = mock(ApiClient.class); } @BeforeEach void initEach() { db.clear(); // 各テスト前にリセット reset(client); } @AfterEach void tearDownEach() { db.rollback(); // 副作用を排除 } @AfterAll void tearDownAll() { db.close(); // リソース解放 } }
3. スローテスト問題と多面的解決策
問題の詳細:
- 外部リソース呼び出し(DB/HTTP)
- 大量データ準備
- 複雑な初期化処理(コンテナ起動など)
解決策:
- タグ分類(@Tag)で除外/含めるグループを制御
- モック/スタブ化で外部依存を置き換え
ApiClient api = mock(ApiClient.class); when(api.call(...)).thenReturn(mockData);
- インメモリDB(H2)で本番DB接続を排除
dependencies { testImplementation 'com.h2database:h2:2.1.214' }
- 並列実行でテスト時間を短縮
test { useJUnitPlatform { systemProperty 'junit.jupiter.execution.parallel.enabled','true' systemProperty 'junit.jupiter.execution.parallel.mode.default','concurrent' } }
- 共通フィクスチャー最適化:重い処理は @BeforeAll、一部を @BeforeEach
- テストのリファクタリング:不要・重複テストの削除、データ量削減
4. 事前条件(Precondition)の記述
目的:特定環境のみ実行したいテストをスキップにする。
@Test void onCiOnly() { Assumptions.assumeTrue( "true".equals(System.getenv("CI")), "CI環境でないためスキップ" ); // CI限定のテスト処理… }
5. パラメータ化テスト
目的:同じロジックを複数の入力で一括検証し、重複を排除。
アノテーション | 用途 |
---|---|
@ValueSource | 単一型のリテラル配列 |
@CsvSource | 複数列の文字列ペア |
@EnumSource | enum の全列挙値 |
@MethodSource | メソッドで提供するストリーム |
@ParameterizedTest @CsvSource({ "racecar, true", "apple, false" }) void isPalindrome(String input, boolean expected) { assertEquals(expected, StringUtils.isPalindrome(input)); }
メリット:
1. テストケースの一覧性向上
2. 新規ケース追加が容易
3. 構造の一貫性維持
6. テストダブル(Dummy / Stub / Fake / Spy / Mock)
目的:外部依存を切り離し、テスト対象の振る舞い検証に集中する。
種類 | 説明 | Mockito 例 |
---|---|---|
Dummy | 値を渡すだけ | new DummyLogger() |
Stub | 固定の戻り値を返す | when(client.get()).thenReturn(data); |
Fake | 簡易実装(インメモリ) | new InMemoryUserRepo() |
Spy | 実オブジェクトを監視 | spy(new ArrayList<>()) |
Mock | 呼び出し検証 | verify(client, times(1)).get(); |
class UserServiceTest { @Mock ApiClient client; @InjectMocks UserService svc; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); when(client.fetchUser("u1")) .thenReturn(new User("u1","Taro")); } @Test void testGetUserName() { assertEquals("Taro", svc.getUserName("u1")); verify(client, times(1)).fetchUser("u1"); } }