Claude Codeでテストコードを安全に作る方法
はじめに
Claude Code は、テストコードの生成が得意です。しかし、闇雲に AI に任せると、**実装より厳しい品質のテストが生成される場合もあります。**本記事では、テストコード生成を安全かつ効果的に行う方法を解説します。
テストコード生成時の注意点
問題1:既存仕様を確認していない
❌ 悪い流れ:
「formatDate() 関数のテストを作ってください」
↓
AI が formatDate() の実装を見て、
テストを作成
↓
しかし、実装と仕様がズレていて、
テストが仕様を守っていない
✅ 改善方法
ステップ1:AI に仕様を先に示す
「formatDate(date: Date): string のテストを作ってください。
仕様:
- 入力:JavaScript の Date オブジェクト
- 出力:「2026-06-22」形式の文字列
- タイムゾーン:常に UTC
- エッジケース:
- 1900-01-01(最古のサポート日付)
- 2999-12-31(最新のサポート日付)
- 無効な日付(null, undefined, invalid date)には Error を throw
テストフレームワーク: Jest
ファイル: tests/utils/date.test.ts
以下のテストケースを網羅してください:
1. 正常系(各月の1日)
2. 月末(28, 29, 30, 31日)
3. うるう年(2月29日)
4. エッジケース(最小・最大日付)
5. エラー系(null, undefined, invalid date)
」
問題2:テスト名が曖昧
❌ テスト名が曖昧な例:
describe('formatDate', () => {
it('works', () => { ... });
it('handles dates', () => { ... });
it('error', () => { ... });
});
問題点:
- テスト失敗時に何が失敗したのか分からない
- テストカバレッジの意図が不明確
✅ テスト名のガイドライン
テスト名は「入力→期待結果」の形式で:
describe('formatDate', () => {
describe('正常系', () => {
it('2026-06-22 の Date を「2026-06-22」 文字列に変換', () => { ... });
it('1900-01-01 の Date を「1900-01-01」 文字列に変換', () => { ... });
});
describe('エッジケース', () => {
it('うるう年の 2月29日 を正しく処理', () => { ... });
it('月末の日付(31日)を正しく処理', () => { ... });
});
describe('エラー系', () => {
it('null を渡すと Error を throw', () => { ... });
it('undefined を渡すと Error を throw', () => { ... });
it('Invalid Date を渡すと Error を throw', () => { ... });
});
});
利点:
- テスト構造が一目瞭然
- 失敗時にどの条件が失敗したかすぐわかる
- テストカバレッジの意図が明確
既存テストとの関係
問題:新しいテストが既存テストと重複
❌ 重複があると:
- テスト実行時間が長くなる
- メンテナンス負荷が増える
- テストが 500 個になる
✅ 回帰テストの戦略
指示:「既存テストを確認した上で、新しいテストを作成
確認事項:
1. tests/utils/date.test.ts に既存テストがあるか
- あれば:既存テストで coverage されていないケースのみ追加
- なければ:全て new で作成
2. 既存実装 src/utils/date.ts を確認
- 実装と仕様にズレがないか
- 既にテストされている範囲は何か
3. テストを追加する場合:
- 既存テストとの重複を避ける
- describe ブロック名は既存と統一
- テストケース数は「最小限」のみ
完成後、以下を報告:
- 追加テスト数
- coverage 変化(既存 vs 新規)
- 新規追加と既存の依存関係(あればなぜ必要か)
」
テスト失敗時の切り分け
問題:テスト失敗したが、実装か仕様か分からない
Error: Expected "2026/06/22" to equal "2026-06-22"
→ これは format が違う?それとも仕様を AI が間違えた?
✅ デバッグ戦略
テスト失敗時:
1. テストを見直す
- AI が仕様を正しく理解していたか
- テストロジックに誤りがないか
2. 実装を見直す
- 実装が仕様通りか
- エッジケースを見落としていないか
3. 仕様自体を見直す
- 設計が正しかったか
- 実運用に対応しているか
参考:各段階で AI に確認させる
「テストケース 『3月31日を正しく処理』が失敗しています:
実装: 2026-03-31 → "2026-3-31"(0 パディング処理なし)
期待: 2026-03-31 → "2026-03-31"(0 パディング処理あり)
以下のいずれが問題か分析してください:
1. 実装が間違っている(format に 0 パディングを追加すべき)
2. テストが間違っている(仕様では 0 パディングは不要)
3. 仕様が曖昧(format を明確に定義すべき)
」
テストコード生成の実際のフロー
段階的なテスト生成
【ステップ1:計画フェーズ】
「getUser(id: number): User | null のテスト計画を提示してください」
AI が以下を出力:
- テストケース一覧
- 各ケースの理由
- データベース mock が必要か
- 依存関係
【ステップ2:確認フェーズ】
「データベース実装を確認して、計画を修正してください」
AI が以下を確認:
- 実装の既存テスト範囲
- エッジケース(user が存在しない場合など)
【ステップ3:実装フェーズ】
「上記の計画でテストコードを実装してください」
AI がテストを生成
【ステップ4:検証フェーズ】
npm test を実行して全て pass を確認
テスト実装の指示例
「getUser() 関数のテストを実装してください。
既存テスト: tests/services/user.test.ts が 5 ケースある
追加する新規ケース:
1. user.role === 'admin' の場合の処理
2. user が削除済み(deleted_at が NULL でない)の場合
3. 権限不足で getUser を呼び出した場合(throw Error)
要件:
- テストフレームワーク: Jest
- DB mock: 既存の setupTestDB() を使用
- テスト前処理: beforeEach() で DB をリセット
- テスト名: 「〇〇 の場合、xxx が成功する」の形式
テスト実装後:
- npm test で全ケースが pass か確認
- 失敗した場合は原因を分析(実装 vs テスト vs 仕様)
」
まとめ
テストコード生成を AI に任せるときの重要なポイント:
事前確認:
- 既存仕様を明確に伝える
- 既存テストを確認させる
- 既存実装との整合性を取る
テスト設計:
- テスト名は「入力→期待結果」
- テストケースを describe で分類
- 重複を避ける
実行と検証:
- テスト失敗時は「実装 vs テスト vs 仕様」の三者を切り分ける
- 各ステップで人間が確認
テストは「品質の最後の砦」です。計画フェーズで吟味してから実装させることで、テストの品質を大きく向上させられます。