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)
  • 大量データ準備
  • 複雑な初期化処理(コンテナ起動など)

解決策:

  1. タグ分類(@Tag)で除外/含めるグループを制御
  2. モック/スタブ化で外部依存を置き換え
    ApiClient api = mock(ApiClient.class);
    when(api.call(...)).thenReturn(mockData);
          
  3. インメモリDB(H2)で本番DB接続を排除
    dependencies {
      testImplementation 'com.h2database:h2:2.1.214'
    }
          
  4. 並列実行でテスト時間を短縮
    test {
      useJUnitPlatform {
        systemProperty 'junit.jupiter.execution.parallel.enabled','true'
        systemProperty 'junit.jupiter.execution.parallel.mode.default','concurrent'
      }
    }
          
  5. 共通フィクスチャー最適化:重い処理は @BeforeAll、一部を @BeforeEach
  6. テストのリファクタリング:不要・重複テストの削除、データ量削減

4. 事前条件(Precondition)の記述

目的:特定環境のみ実行したいテストをスキップにする。

@Test
void onCiOnly() {
  Assumptions.assumeTrue(
    "true".equals(System.getenv("CI")),
    "CI環境でないためスキップ"
  );
  // CI限定のテスト処理…
}
  

5. パラメータ化テスト

目的:同じロジックを複数の入力で一括検証し、重複を排除。

アノテーション用途
@ValueSource単一型のリテラル配列
@CsvSource複数列の文字列ペア
@EnumSourceenum の全列挙値
@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");
  }
}