ai-technology

Claude Codeでテストコードを安全に作る方法

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 に任せるときの重要なポイント:

事前確認

テスト設計

実行と検証

テストは「品質の最後の砦」です。計画フェーズで吟味してから実装させることで、テストの品質を大きく向上させられます。