Claude Codeのテスト生成で品質を担保するには、CLAUDE.mdでルール定義、Hooksで自動実行、Strykerのミューテーションテストで検証——この3層の仕組みが必要だ。プロンプトの工夫だけでは限界がある。
「テスト書いて」と頼めば数分で20個以上のテストが生成される。問題は、その半分が意味のないテストかもしれないことだ。Claude Codeが生成するテストをStrykerにかけると、ミューテーションスコアが60%台になることがある。見た目はきれいなのに、コードの変更を検出できないテストが4割近くあるわけだ。
AI生成テストは「カバレッジの数字」を上げるのは得意だが、「本当にバグを見つけるテスト」を書くのは苦手だ。同じコードパスを何度もテストする重複、境界値を無視したハッピーパスの羅列、存在しないAPIを呼ぶハルシネーション——これらはClaude Codeにテストを任せたときの典型的な問題だ。
この記事では、僕が実際に構築した仕組みを解説する。CLAUDE.mdでルールを定義し、Hooksで自動実行を強制し、TDDワークフローをサブエージェントで実装し、ミューテーションテストで品質を定量的に検証する方法だ。
なぜClaude Codeにテストを任せるのか
テストは開発で最も「面倒だけど必要」な作業だ。Claude Codeはこの面倒な部分を劇的に加速できる。
Claude Codeがテスト生成で強い理由:
- コードベース全体を読める ——関数のシグネチャだけでなく、呼び出し元や依存関係まで把握してテストを書く
- テスト→実行→修正のループを自律的に回せる ——失敗したテストを自分で修正し、全パスするまで繰り返す。GitHub Copilotの補完とは根本的に違う
- 既存テストのパターンを学習する ——プロジェクト内の既存テストスタイルに合わせたテストを生成する
OpenObserve社はClaude Codeを使ったテスト自動化で380テストから700以上に拡大し、フレーキーテスト(不安定テスト)を85%削減した。特徴分析の時間は45〜60分から5〜10分に短縮された。
ただし「テスト書いて」と丸投げするだけでは品質は担保できない。仕組みで品質を強制する必要がある。
Claude Codeが生成するテストの問題点
Claude Codeにテスト生成を任せると、以下の問題が頻繁に発生する。
1. デッドウェイトテスト(無意味なテスト)
同じコードパスを複数のテストが重複してカバーする。カバレッジは上がるが、バグ検出力は変わらない。URLスラッグ生成関数のテストを頼むと、微妙に違う入力で同じコードパスを通っているだけのテストが混ざることがある。
2. ハッピーパス偏重
正常系のテストばかり生成し、エラーパスや境界値を無視する。HTTP 500エラー、空配列、null入力などのエッジケースが抜けがちだ。テストは全部パスしたけど、最初の不正な入力で本番が落ちるところだった。
3. ハルシネーション
関数が Promise を返すと誤って仮定する、存在しないメソッドを呼ぶ、間違ったimportパスを使うなど。テスト自体がコンパイルエラーになるケースもある。
4. デフォルトでテスト後回し
明示的に指示しないと、Claude Codeは実装コードを先に書いてテストを後回しにする。TDDを求めても、コンテキストが埋まるにつれて実装ファーストに戻る。
5. フレームワーク誤認
VitestプロジェクトなのにJestのAPIを使う、不要なモックファイルを生成する、テスト設定ファイルを勝手に変更する。Jestをインストールしていないプロジェクトで jest.mock() が出てきたときは流石に笑った。
これらの問題を「注意して指示する」で解決しようとしても限界がある。仕組みで強制するのが正解だ。
CLAUDE.mdでテストルールを定義する
CLAUDE.mdにテストルールを書くことで、セッション開始時から一貫したテスト品質を維持できる。CLAUDE.mdの設計パターンについてはコンテキスト管理ガイドも参照してほしい。
# Testing Rules
## Framework
- Vitest 4.1 + React Testing Library + MSW for API mocking
- Test files: `__tests__/{module}.test.ts` (colocated with source)
- Config: vitest.config.ts (already configured, do not modify)
## Test Quality Rules
- NEVER write tests that only cover the happy path. Every test file must include edge cases
- ALWAYS test error paths: null input, empty arrays, network failures, validation errors
- NEVER mock what you don't own — use MSW for HTTP, real implementations for utilities
- ONE assertion focus per test. "should handle X" not "should handle X and Y and Z"
- ALWAYS include a descriptive test name that explains the expected behavior
## Test Structure
- Arrange-Act-Assert pattern for every test
- Use test.each() for parameterized tests instead of duplicating similar tests
- Group related tests with describe() blocks matching the function/component name
## Forbidden
- Do NOT modify vitest.config.ts or test setup files without asking
- Do NOT add snapshot tests (they pass trivially and catch nothing useful)
- Do NOT use jest.* APIs — this project uses Vitest (vi.*)
- Do NOT write tests after implementation. Write tests FIRST (TDD)
.claude/rules/ でファイルパターン別にルールを適用
パススコープルールを使えば、テストファイルを編集するときだけ追加ルールを読み込ませることができる。
<!-- .claude/rules/testing.md -->
---
paths:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**"
---
# Test File Rules
- Import from vitest: describe, it, expect, vi, beforeEach, afterEach
- Import from @testing-library/react: render, screen, fireEvent, waitFor
- Import from msw: http, HttpResponse for API mocks
- Always clean up: afterEach(() => cleanup())
- Prefer userEvent over fireEvent for user interactions
このルールはClaude Codeがマッチするテストファイルにアクセスしたときだけ自動的にコンテキストに読み込まれる。通常の開発時にはトークンを消費しない。
なお、初期バージョンのClaude Codeでは paths: のYAMLパースに問題があった(#17204、#13905)。現在はほぼ解消されているので、上記のYAMLリスト形式を使えばOKだ。Cursorも併用している場合、CursorはClaude Codeの paths: ではなく globs: を使う点に注意。
Hooksでテスト実行を自動化する
CLAUDE.mdだけではルール違反を防げない。Hooksを組み合わせることで、テスト実行を自動化し、品質を機械的に検証できる。
ファイル編集後にテストを自動実行する
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx vitest run --reporter=verbose 2>&1 | tail -20"
}
]
}
]
}
}
ファイルを編集するたびにVitestが自動実行される。Claudeは失敗結果を即座に確認し、自己修正できる。
セッション停止前にテスト全パスを強制する
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Run the full test suite with `npx vitest run`. If any test fails, fix it before stopping. Do not stop until all tests pass.",
"timeout": 120
}
]
}
]
}
}
Stop フックに agent タイプを使うと、Claudeが停止する前にサブエージェントがテストを実行する。失敗があれば自動で修正してから停止する。
組み合わせの効果
| フック | タイミング | 効果 |
|---|---|---|
PostToolUse + `Write | Edit` | ファイル編集のたび |
Stop + agent | セッション停止前 | テスト全パスを保証 |
この2つを組み合わせるだけで、「テストが通らないコードがコミットされる」状況をほぼ完全に防げる。このコンボを導入すると、リグレッションを未然に防げるケースが増える。
実践:既存コードにテストを追加する
既存のNext.jsプロジェクトにテストを追加する実践的なワークフロー。
Step 1: テスト対象を特定する
このプロジェクトでテストカバレッジが低い関数を見つけて。
とくにビジネスロジック(lib/、utils/、actions/)を優先して。
Claude Codeはコードベースを走査し、テストが存在しないモジュールをリストアップする。
Step 2: 優先度をつけてテストを生成する
lib/auth/validate-session.ts のテストを書いて。
エッジケース(期限切れトークン、不正フォーマット、null入力)を含めて。
一度に全ファイルのテストを依頼するのではなく、 1モジュールずつ依頼する のがコツだ。コンテキストが集中し、品質の高いテストが生成される。
Step 3: 生成されたテストをレビューする
Claude Codeが生成したテストで確認すべきポイント:
- 各テストが独立しているか ——テスト間で状態を共有していないか
- エッジケースがカバーされているか ——正常系だけでなくエラーパスがあるか
- アサーションが具体的か ——
toBeTruthy()ではなくtoBe(true)やtoEqual(expected)を使っているか - モックが最小限か ——必要以上にモックしていないか
生成したテストにデッドウェイト(重複テスト)がないか確認して。
同じコードパスを複数テストしているものがあれば削除して。
TDDワークフロー:テストファーストを強制する
Claude CodeでTDD(テスト駆動開発)を実践するには、明示的な指示とサブエージェントの活用が効果的だ。
基本的なTDDプロンプト
TDDで進めて。
1. まずテストを書く(RED)— テストは失敗する状態
2. テストをパスする最小限の実装を書く(GREEN)
3. リファクタリングする(REFACTOR)
各ステップでテストを実行して結果を見せて。
コンテキスト汚染の問題と対策
単一セッションでRed→Green→Refactorを回すと、各フェーズのコンテキストが混ざって品質が下がる。これを コンテキスト汚染 と呼ぶ。トークン管理の観点からも重要な問題だ。
対策として、各フェーズをサブエージェントに分離する方法がある。
<!-- .claude/commands/tdd.md -->
TDDワークフローを実行する。
1. テストライターエージェント(RED):
- $ARGUMENTS の機能要件からテストを書く
- テストが失敗することを確認する
2. 実装エージェント(GREEN):
- 生成されたテストだけを読む
- テストをパスする最小限のコードを書く
3. リファクタラーエージェント(REFACTOR):
- テストと実装の両方を読む
- テストが通ったままリファクタリングする
各エージェントが独立したコンテキストで動くため、フェーズ間の干渉を防げる。カスタムスラッシュコマンドの詳細はコマンドチートシートを参照。
生成テストの品質を検証する
テストが「存在する」ことと「バグを見つけられる」ことは別だ。ミューテーションテストで、生成テストの品質を定量的に検証できる。
ミューテーションテストとは
ソースコードに小さな変更(ミューテーション)を加えて、テストがそれを検出できるかを調べる手法だ。
if (a > b)→if (a >= b)に変更- テストが失敗する → ミュータント「殺された」(テストが有効)
- テストが通る → ミュータント「生存」(テストが不十分)
Strykerでミューテーションテストを実行する
StrykerとVitestランナープラグインをインストールする:
npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runner
npx stryker run
// stryker.config.mjs
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
export default {
testRunner: "vitest",
mutate: ["src/lib/**/*.ts", "!src/lib/**/*.test.ts"],
reporters: ["html", "clear-text"],
vitest: {
configFile: "vitest.config.ts",
},
};
結果の読み方
Mutation score: 78.5%
Killed: 51 | Survived: 14 | No coverage: 3
- 85%以上: テスト品質は良好
- 70-85%: 一部のエッジケースが漏れている
- 70%未満: テストの見直しが必要
生存ミュータントをClaudeに修正させる
Strykerのミューテーションレポート(stryker-report/mutation.html)を読んで。
生存ミュータントに対応するテストケースを追加して。
Claude Codeは生存ミュータントの位置を特定し、そのコードパスをカバーするテストを追加する。ミューテーションスコアが85%を超えるまで繰り返す。
よくある質問
Claude CodeはVitestをそのまま使える?
使える。Claude Codeはプロジェクトの設定からテストフレームワークを自動検出する。package.json に vitest があって vitest.config.ts があれば、Vitest APIを自動的に使う。ただしJestのAPIと混同することがあるので、CLAUDE.mdでフレームワークを明示するのが重要だ。
JestでもこのパターンはClaude Codeで使える?
使える。CLAUDE.mdのルール、Hooks設定、TDDワークフローはフレームワークに依存しない。HookコマンドのVitest部分を npx jest に置き換えて、CLAUDE.mdのフレームワークセクションを更新するだけ。StrykerもJestランナーをサポートしている。
PostToolUse Hookはどれくらいトークンを消費する?
PostToolUse HookはVitestを実行して出力の末尾20行をClaudeに返す。テスト50〜100個の一般的なプロジェクトなら、ファイル編集1回あたり200〜400トークン程度だ。Stop Hookのagentサブエージェントはもっと重い——修正量にもよるが2,000〜5,000トークン程度を見込んでおくといい。
TDDのサブエージェント方式は追加トークンコストに見合う?
小さなバグ修正やシンプルなユーティリティには過剰だ。認証フロー、データ変換、複数ステップのワークフローなど、複雑なロジックの新機能開発なら元は取れる。インターフェース設計が重要なモジュールに限定して使うのが僕のやり方だ。
ミューテーションスコアはどれくらいを目指すべき?
85%が現実的な目標だ。70%以下はテストに実質的なギャップがある。90%超えは些細なコードパスまでテストしている可能性が高い。ミューテーションテストはビジネスロジック(lib/、utils/、actions/)に集中させて、UIコンポーネントや設定ファイルには使わなくていい。
Claude CodeはStrykerのHTMLレポートを直接読める?
npx stryker run のテキスト出力は読める。生存ミュータントの詳細も含まれる。HTMLレポートについては、関連箇所を貼り付けるか内容を説明する必要がある。Stryker設定の clear-text レポーターを使えば、Claudeが生存ミュータントを特定するのに十分な情報が得られる。
Claude Codeがテスト設定ファイルを変更するのを防ぐには?
CLAUDE.mdに「vitest.config.tsやテストセットアップファイルを確認なしに変更するな」と明記する。Claude Codeはこの指示を確実に守る。さらに安全策として、該当ファイルパスを .claudeignore に追加する方法もある。
モノレポでも使える?
使える。ただしパッケージごとに別のCLAUDE.mdテストルールを用意するか、.claude/rules/ のパススコープルールを使う必要がある。Hooks設定はプロジェクト全体に適用されるので、Vitestコマンドを特定パッケージに向ける: npx vitest run --project=packages/core。
まとめ
Claude Codeのテスト生成は「プロンプトの工夫」だけでは品質を担保できない。仕組みで強制するのが正解だ。
| レイヤー | 役割 | 設定 |
|---|---|---|
| CLAUDE.md | テストルールの定義(フレームワーク、品質基準、禁止事項) | 5分 |
| Hooks | テスト実行の自動化(PostToolUse + Stop) | 5分 |
| TDDワークフロー | テストファーストの強制(サブエージェント分離) | 10分 |
| ミューテーションテスト | 生成テストの品質検証(Stryker) | 15分 |
CLAUDE.mdでルールを定義し、Hooksで自動実行を強制し、ミューテーションテストで品質を検証する。この3層を整えれば、Claude Codeは「カバレッジだけ高い無意味なテスト」ではなく「バグを見つけるテスト」を生成するようになる。セットアップは合計35分程度——それ以降、ダメなテストのデバッグに費やす時間を何時間も節約できている。
関連記事: