32blogby StudioMitsu
claude-code15 min read

Claude Codeでテストコードを自動生成する実践ガイド

Claude CodeでJest・Vitestのテストを自動生成する実践手順。TDD的アプローチ・カバレッジ設定・失敗したテストを自動修正させるワークフローを全公開。

claude-codeテスト自動化vitestjestTDDTypeScript
目次

Claude Codeに「テスト書いて」と頼んだら、モックだらけで実際には何もテストしていないコードが生成された。僕もこの問題に最初ぶつかった。

typescript
// Claudeが生成した「テスト」の実態
vi.mock('../utils/fetchUser')
vi.mock('../utils/validateInput')
vi.mock('../lib/database')

it('should work', () => {
  expect(mockFetchUser).toHaveBeenCalled()
})

これはテストじゃない。モックの呼び出し確認だ。どう指示すれば意味のあるテストが出てくるか、ここ数ヶ月で試行錯誤した記録を全部書く。

この記事を読めば次のことがわかる。

  • Claude Codeに意味のあるテストを生成させるプロンプト構造
  • CLAUDE.mdへのテスト設定の書き方
  • VitestでTDDループを自動化する手順
  • 失敗したテストをClaudeに自動修正させるコマンド
  • モックが過剰になる問題の根本的な解決法

Claude Codeのテスト生成は他のツールと何が違うのか?

GitHub Copilotや他のAIコード補完ツールでテストを生成すると、関数のシグネチャだけを見てボイラープレートを埋めてくれる。便利だけど、実際のロジックを理解したテストにはなりにくい。

Claude Codeの強みは「コードの意図を読んで」テストを生成できる点にある。ただし、それを引き出すには正しい指示が必要だ。

何も考えずに「テスト書いて」と頼むと、Claudeはコードの「動作」より「インターフェース」に注目する。結果として依存関係をすべてモックして、入出力の形だけを確認するようなテストが出てくる。それはそれで間違いじゃないが、バグを検出する能力がほぼゼロだ。

Claude Codeに生成させるべきテストの要件を最初に決めておく必要がある。その要件をCLAUDE.mdとプロンプトの両方で伝えることが、質の高いテスト生成の出発点になる。


CLAUDE.mdにテスト設定を書くとどう変わるか?

CLAUDE.mdに以下の設定を書いておくと、Claude Codeがプロジェクト全体のテスト方針を常に参照するようになる。

markdown
## テスト方針

### 使用ツール
- テストランナー: Vitest
- アサーション: Vitest built-in (expect)
- モックライブラリ: vi.fn(), vi.spyOn() — ただし最小限に
- カバレッジ: @vitest/coverage-v8

### テスト作成のルール
1. 外部IO(DB・API・ファイルシステム)のみモック対象とする
2. ユーティリティ関数・純粋関数はモックしない
3. テストケース名は「〇〇のとき、〇〇を返す」の形式で書く
4. エッジケース(空配列・null・境界値)を必ず含める
5. テストファイルは実装ファイルと同じディレクトリに置く(`*.test.ts`)

### カバレッジ閾値
- statements: 80%
- branches: 75%
- functions: 80%
- lines: 80%

### 禁止事項
- `as any` の使用
- 実装の詳細(内部変数名・private メソッド)をテストしない
- テストのためだけにコードを変更しない

このCLAUDE.mdの設定は、個別のプロンプトよりも先に読まれる。「モックは外部IOだけ」というルールを書いておくだけで、生成されるテストの質が目に見えて変わる。


「テスト書いて」だけじゃ失敗する——正しいプロンプトの構造は?

Claude Codeへのテスト生成プロンプトには、次の4要素を含める。

1. テスト対象の明示

`src/utils/calculateDiscount.ts` のテストをVitestで書いてください。

2. テスト範囲の制約

外部依存はありません。モックは使わないでください。
純粋関数なので、入力と出力の組み合わせを網羅的にテストしてください。

3. エッジケースの指定

以下のケースを必ずカバーしてください:
- discountRate が 0 のとき
- discountRate が 1.0(100%引き)のとき
- price が 0 のとき
- price が負の値のとき(エラーを期待)

4. テスト名の形式

テスト名は「〇〇のとき、〇〇を返す」の日本語形式で統一してください。

実際に生成されたテストの例を見てほしい。

typescript
import { describe, it, expect } from 'vitest'
import { calculateDiscount } from './calculateDiscount'

describe('calculateDiscount', () => {
  describe('正常系', () => {
    it('discountRateが0.2のとき、価格の80%を返す', () => {
      expect(calculateDiscount(1000, 0.2)).toBe(800)
    })

    it('discountRateが0のとき、元の価格をそのまま返す', () => {
      expect(calculateDiscount(1000, 0)).toBe(1000)
    })

    it('discountRateが1.0のとき、0を返す', () => {
      expect(calculateDiscount(1000, 1.0)).toBe(0)
    })
  })

  describe('境界値', () => {
    it('priceが0のとき、0を返す', () => {
      expect(calculateDiscount(0, 0.2)).toBe(0)
    })
  })

  describe('異常系', () => {
    it('priceが負の値のとき、Errorをスローする', () => {
      expect(() => calculateDiscount(-100, 0.2)).toThrow('Price must be non-negative')
    })

    it('discountRateが1を超えるとき、Errorをスローする', () => {
      expect(() => calculateDiscount(1000, 1.5)).toThrow('Discount rate must be between 0 and 1')
    })
  })
})

「テスト書いて」の一言との差は歴然だ。


VitestでTDDループを自動化するには?

Claude Codeを使ったTDDループは、次の手順で回す。

Step 1: 仕様をテストとして書かせる

実装前にClaude Codeに仕様書からテストを生成させる。

以下の仕様に基づいてVitestのテストを書いてください。実装はまだ作りません。

仕様:
- `parseJapaneseDate(str: string): Date` 関数を作る
- "2026年2月26日" 形式の文字列をDateオブジェクトに変換する
- 不正なフォーマットのときはInvalidDateErrorをスローする
- タイムゾーンはJST(UTC+9)で固定

Step 2: テストが失敗することを確認

bash
npx vitest run src/utils/parseJapaneseDate.test.ts

当然、実装がないので全部失敗する。これが出発点だ。

Step 3: 実装を生成させる

`src/utils/parseJapaneseDate.test.ts` のテストをすべてパスする実装を書いてください。
テストを変更せずに実装側だけで解決してください。

Step 4: --watch モードで確認しながら修正

bash
npx vitest --watch src/utils/parseJapaneseDate.test.ts

このループを package.json に登録しておくと便利だ。

json
{
  "scripts": {
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest run --coverage"
  }
}

カバレッジ閾値はどう設定してCIに組み込むか?

vitest.config.ts にカバレッジ設定を入れる。

typescript
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      thresholds: {
        statements: 80,
        branches: 75,
        functions: 80,
        lines: 80,
      },
      exclude: [
        'node_modules/**',
        'src/**/*.d.ts',
        'src/**/*.stories.tsx',
        'src/app/**', // Next.js App Routerのページは除外
      ],
    },
  },
})

GitHub ActionsでCI統合する設定も一緒に作っておく。

yaml
# .github/workflows/test.yml
name: Test

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run test:coverage
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage/coverage-final.json

カバレッジ閾値を下回るとCIが失敗するので、テストを書かずにPRを出しにくくなる。チームでの品質担保に効く。


失敗したテストをClaudeに自動修正させるにはどうするか?

テストが失敗したとき、出力をそのままClaudeに渡して修正させるワークフローが便利だ。

Claude Codeのターミナル統合を使えば、次のような流れになる。

bash
# テストを実行して失敗ログを取得
npx vitest run 2>&1 | tee /tmp/test-output.txt

その後、Claude Codeに渡す。

以下のテスト失敗ログを見て、実装コードを修正してください。
テスト自体は変更しないでください。

---
[/tmp/test-output.txt の内容をペースト]
---

修正対象: src/utils/parseJapaneseDate.ts

さらに踏み込んで、package.json にカスタムスクリプトを用意する方法もある。

json
{
  "scripts": {
    "test:fix": "vitest run 2>&1 | npx claude-code 'テストが失敗しています。実装コードのみを修正してテストをパスさせてください。テストファイルは変更しないでください。'"
  }
}

Claude Code CLIが使える環境ならこのパイプラインが強力だ。失敗ログを自動的にコンテキストとして渡せる。


E2EテストにPlaywrightを使う場合のプロンプトは?

E2Eテストは単体テストより生成難易度が高いが、Claude Codeは画面の動作を自然言語で説明するとPlaywrightのコードを生成できる。

プロンプトの書き方が重要になる。

以下のユーザーストーリーに基づいてPlaywrightのテストを書いてください。

ユーザーストーリー:
- ユーザーがログインページにアクセスする
- メールアドレスとパスワードを入力してSubmitボタンをクリックする
- ダッシュボードページに遷移することを確認する
- ページタイトルが「ダッシュボード」であることを確認する

前提:
- ベースURL: http://localhost:3000
- テストユーザー: test@example.com / password123
- セレクターはdata-testidを使う(クラス名やid属性は使わない)

生成されるコードの例。

typescript
import { test, expect } from '@playwright/test'

test.describe('ログイン機能', () => {
  test('正しい認証情報でダッシュボードに遷移する', async ({ page }) => {
    await page.goto('/login')

    await page.getByTestId('email-input').fill('test@example.com')
    await page.getByTestId('password-input').fill('password123')
    await page.getByTestId('submit-button').click()

    await expect(page).toHaveURL('/dashboard')
    await expect(page).toHaveTitle('ダッシュボード')
  })
})

data-testid を使うよう指定するのがポイントだ。クラス名やid属性は実装の変更で壊れやすいが、テスト用のdata属性は安定している。


モックが過剰になる問題はどう解決するか?

モックが過剰になる問題の根本原因は、Claude Codeが「テスト実行を安全にしようとする」からだ。外部依存があると判断したものをすべてモックする傾向がある。

解決策1: モック禁止を明示する

このユーティリティ関数は副作用がありません。
vi.mock() は使わないでください。

解決策2: テスト戦略を先に宣言させる

テストを書く前に、どの依存関係をモックするか、しないかの方針を説明してください。
その方針を確認してから実装に進んでください。

解決策3: CLAUDE.mdに「モックの判断基準」を書く

markdown
## モックの判断基準

モックする対象:
- データベース接続
- 外部HTTP API
- ファイルシステム操作
- メール送信・SMS送信

モックしない対象:
- 純粋関数(入力と出力だけを持つ関数)
- 日付処理(Date.now()以外)
- 文字列・数値操作
- 配列・オブジェクト操作

このCLAUDE.mdの記述が「プロジェクトの文化」としてClaudeに伝わり、プロンプトごとに指定しなくても一貫したテストが生成されるようになる。


まとめ

Claude Codeでテストを自動生成するときの核心は「何をテストしないか」を明示することだ。

  • CLAUDE.mdにモックの判断基準を書く — プロジェクト全体で一貫性が出る
  • プロンプトに4要素(対象・制約・エッジケース・命名規則)を含める — 具体的な指示が質を決める
  • テストを先に書かせてから実装を生成させる — TDDループで整合性が取れる
  • 失敗ログをそのままClaudeに渡す — 自動修正サイクルで効率が上がる
  • カバレッジ閾値をCIに組み込む — テストを書く文化が定着する

「テスト書いて」の一言から卒業して、CLAUDE.mdとプロンプト設計に投資すると、テストの質が別次元になる。