第10回:Claude Codeでテストコードを書く|Androidユニットテストを効率化する方法

社員ブログ

※Claude Codeを使用して記事を作成しています。

前回の記事では、Claude Codeを使った実際の開発フローを紹介しました。

今回は、多くの開発者が「重要だとわかっているけど、後回しにしがち」な、

「テストコード」

をテーマに取り上げます。

Claude Codeを使うことで、テストコードの作成がどう変わるのか、実例を交えて解説します。

なぜテストコードは後回しになるのか

テストコードが書かれない理由は、主に3つあります。

  1. 書き方がわからない
  2. 時間がかかる
  3. どこから始めればいいかわからない

特に初心者にとっては、テストコードは「書けるようになるまでのハードルが高い」領域です。

Claude Codeは、このハードルを大きく下げてくれます。

Claude Codeがテストに向いている理由

テストコードは、以下の特徴があります。

  • パターンが決まっている
  • 構造が繰り返しになりやすい
  • 「入力→期待する結果」が明確

これは、Claude Codeが得意とする作業と一致しています。

「何をテストするか」を伝えれば、テストコードの雛形を一気に生成できます。

実例:ViewModelのユニットテストを書く

ここでは、メモアプリのViewModelに対するユニットテストを作成する流れを紹介します。

テスト対象のコード

以下のViewModelをテストします。

@HiltViewModel
class MemoViewModel @Inject constructor(
    private val getMemoListUseCase: GetMemoListUseCase,
    private val deleteMemoUseCase: DeleteMemoUseCase
) : ViewModel() {

    private val _uiState = MutableStateFlow<MemoUiState>(MemoUiState.Loading)
    val uiState: StateFlow<MemoUiState> = _uiState.asStateFlow()

    init {
        loadMemoList()
    }

    fun loadMemoList() {
        viewModelScope.launch {
            getMemoListUseCase()
                .onSuccess { memos ->
                    _uiState.value = MemoUiState.Success(memos)
                }
                .onFailure {
                    _uiState.value = MemoUiState.Error("取得に失敗しました")
                }
        }
    }

    fun deleteMemo(id: String) {
        viewModelScope.launch {
            deleteMemoUseCase(id)
                .onSuccess { loadMemoList() }
                .onFailure {
                    _uiState.value = MemoUiState.Error("削除に失敗しました")
                }
        }
    }
}

ステップ1:テストケースを洗い出してもらう

まず、何をテストすべきかをClaude Codeに相談します。

【質問例】
以下のViewModelに対して、ユニットテストを書きたいです。
まず、テストすべきケースを洗い出してください。

(ViewModelのコードを貼り付ける)

すると、以下のようなテストケースを提案してくれます。

【提案されるテストケース例】

loadMemoList()
- 正常取得時:uiStateがMemoUiState.Successになること
- 取得失敗時:uiStateがMemoUiState.Errorになること
- 初期状態:uiStateがMemoUiState.Loadingであること

deleteMemo()
- 削除成功時:loadMemoList()が再実行されること
- 削除失敗時:uiStateがMemoUiState.Errorになること

「何をテストすべきか」の整理にClaude Codeを使う。
これだけでも、大きな時間短縮になります。

ステップ2:テストコードを生成してもらう

テストケースが決まったら、コードを依頼します。

【質問例】
以下の環境で、先ほど洗い出したテストケースの
ユニットテストを書いてください。

【環境】
- 言語:Kotlin
- テストライブラリ:JUnit4、MockK、Turbine
- 非同期処理:Coroutine(StandardTestDispatcher使用)
- DIフレームワーク:Hilt(テストでは使わない)

【テスト対象】
(ViewModelのコードを貼り付ける)

ステップ3:生成されたコードを確認する

Claude Codeが生成するテストコードは、以下のような形になります。

@OptIn(ExperimentalCoroutinesApi::class)
class MemoViewModelTest {

    private val testDispatcher = StandardTestDispatcher()

    private val getMemoListUseCase: GetMemoListUseCase = mockk()
    private val deleteMemoUseCase: DeleteMemoUseCase = mockk()

    private lateinit var viewModel: MemoViewModel

    @Before
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
    fun `メモ取得成功時、uiStateがSuccessになること`() = runTest {
        // Arrange
        val memoList = listOf(Memo(id = "1", title = "テスト"))
        coEvery { getMemoListUseCase() } returns Result.success(memoList)

        // Act
        viewModel = MemoViewModel(getMemoListUseCase, deleteMemoUseCase)
        testDispatcher.scheduler.advanceUntilIdle()

        // Assert
        val state = viewModel.uiState.value
        assertTrue(state is MemoUiState.Success)
        assertEquals(memoList, (state as MemoUiState.Success).memos)
    }

    @Test
    fun `メモ取得失敗時、uiStateがErrorになること`() = runTest {
        // Arrange
        coEvery { getMemoListUseCase() } returns Result.failure(Exception())

        // Act
        viewModel = MemoViewModel(getMemoListUseCase, deleteMemoUseCase)
        testDispatcher.scheduler.advanceUntilIdle()

        // Assert
        assertTrue(viewModel.uiState.value is MemoUiState.Error)
    }

    @Test
    fun `削除成功時、メモ一覧が再取得されること`() = runTest {
        // Arrange
        val memoList = listOf(Memo(id = "1", title = "テスト"))
        coEvery { getMemoListUseCase() } returns Result.success(memoList)
        coEvery { deleteMemoUseCase(any()) } returns Result.success(Unit)

        viewModel = MemoViewModel(getMemoListUseCase, deleteMemoUseCase)
        testDispatcher.scheduler.advanceUntilIdle()

        // Act
        viewModel.deleteMemo("1")
        testDispatcher.scheduler.advanceUntilIdle()

        // Assert
        coVerify(exactly = 2) { getMemoListUseCase() }
    }

    @Test
    fun `削除失敗時、uiStateがErrorになること`() = runTest {
        // Arrange
        coEvery { getMemoListUseCase() } returns Result.success(emptyList())
        coEvery { deleteMemoUseCase(any()) } returns Result.failure(Exception())

        viewModel = MemoViewModel(getMemoListUseCase, deleteMemoUseCase)
        testDispatcher.scheduler.advanceUntilIdle()

        // Act
        viewModel.deleteMemo("1")
        testDispatcher.scheduler.advanceUntilIdle()

        // Assert
        assertTrue(viewModel.uiState.value is MemoUiState.Error)
    }
}

生成されたコードを確認する際は、以下のポイントを見てください。

【確認ポイント】
- Arrange / Act / Assert の構造が揃っているか
- MockKの設定が正しいか(coEvery vs every)
- Coroutineの処理が正しく待たれているか
- テストの命名が何をテストしているか伝わるか

ステップ4:わからない部分を質問する

生成されたコードで理解できない部分があれば、そのまま聞けます。

【質問例】
testDispatcher.scheduler.advanceUntilIdle()
は何をしているのか、初心者向けに説明してください。
【質問例】
coEvery と every の使い分けを教えてください。

テストコードは、書けるようになるだけでなく、
「なぜそう書くのか」を理解することが重要です。

Claude Codeを使って、コードと一緒に知識も深めていきましょう。

テストコードでClaude Codeを使う3つの場面

テスト作成以外にも、Claude Codeが活躍する場面があります。

1. テストが失敗した原因を調べる

【質問例】
以下のテストが失敗しています。
エラーメッセージと合わせて、原因を教えてください。

【エラーメッセージ】
(エラーを貼り付ける)

【テストコード】
(コードを貼り付ける)

2. カバレッジを上げるためのケースを追加する

【質問例】
以下のテストコードに、
不足しているテストケースがあれば追加してください。

(テストコードを貼り付ける)

3. テストコードをリファクタリングする

【質問例】
以下のテストコードを読みやすく整理してください。
重複している部分があれば共通化も検討してください。

(テストコードを貼り付ける)

よくある失敗:生成されたコードをそのまま信じる

テストコードでも、注意点は同じです。

Claude Codeが生成したテストコードが、必ずしも正しい動作を保証するわけではありません。

特に注意したいのは、

- モックの設定が不完全で、テストが常に成功してしまう
- 非同期処理の待ち方が間違っていて、テストが不安定になる
- 実際の仕様と異なるケースをテストしている

といったケースです。

「テストが通っている」=「コードが正しい」ではない

ということを、必ず意識してください。

まとめ

Claude Codeを使ったテストコード作成のポイントをまとめます。

  • まずテストケースの洗い出しをClaude Codeに手伝ってもらう
  • 環境・ライブラリを明示してコードを依頼する
  • 生成されたコードは必ず自分で確認する
  • わからない部分はそのまま質問して理解を深める
  • テストが通ることと、正しいことは別物と意識する

テストコードは、書き始めるハードルが高いだけで、一度書けるようになると開発の安心感が大きく変わります。

Claude Codeをうまく活用して、テストを書く習慣を作っていきましょう。

タイトルとURLをコピーしました