0:00 0:00
記事
Claude Codeにテストコードを生成させる依頼文と注意点:観点を指定して網羅率を上げる
Claude Codeにテストコードを生成させるときの依頼文テンプレートと、AI生成テストを盲信しないためのレビュー観点を整理します。ユニット・境界値・例外・モックの依頼文、Tautology Test(自分で自分を肯定するテスト)の見抜き方、テストカバレッジを過信しない理由まで扱います。
結論:Claude Codeに「観点・対象・モック方針」を指定して書かせ、テスト自体を必ず人間が読む
Claude Codeはnpm testやpytestを自分で実行できるので、**「テストを書かせて、走らせて、結果を見て直す」**まで一気通貫でやってくれます。ただし、観点を指定せずに「テストを書いて」と頼むと、正常系1〜2ケースだけの薄いテストや、実装をそのまま読み返すだけのトートロジーテストが返ってきます。
公式のBest Practicesも、「add tests for foo.py」より「write a test for foo.py covering the edge case where the user is logged out. avoid mocks.」のようにスコープと条件を明示するよう推奨しています。
本記事では次の4テンプレートと、AI生成テストを盲信しないためのレビュー観点を扱います。
- 新規ユニットテスト:観点(正常系・境界値・異常系)を指定して書かせる
- 既存実装に後追いテスト:仕様を実装から逆算させない
- モック方針指定:実DBやfetchを触ってよい範囲を明示する
- 回帰テスト追加:本番バグの再発防止テストだけ追加する
依頼文の骨格はClaude Codeに伝わるプロンプトの基本構造、Red→Green→Refactorの順序で回す全体像はClaude Codeでテスト駆動開発(TDD)を回す手順、完了条件と禁止事項の書き分けはClaude Codeの成功率を上げる制約条件と受け入れ条件の作り方を参照してください。本記事は生成済みテストの質を判定するレビュー側にフォーカスします。
なぜ「テストを書いて」だけでは弱いのか
Claudeに観点を渡さないと、次のような薄いテストが返ってきがちです。
- 関数をそのまま呼び返すだけで、入出力アサートが
toBeDefined()止まり - 正常系1ケースのみで境界値(空配列・null・最大値・タイムゾーン違い)が抜ける
- モックでべったり固めたことで、本物のバグが起きうる経路を1ミリも通っていない
- 既存実装のロジックをそのままテストにコピーしただけのトートロジー(実装が間違っていても通る)
特に最後のトートロジーテストが一番厄介です。実装と同じ間違いをテストにも書いてしまうので、テストは緑なのにバグが残ります。これを防ぐには、依頼文で**「仕様(外側から見た振る舞い)」を入力にし、「実装の動き」を入力にしない**のが鉄則です。
テンプレ1:新規ユニットテスト(観点指定)
新しい関数のユニットテストでは、観点をこちら側から先に挙げるのが効きます。
## 目的
@src/utils/parseDuration.ts の parseDuration 関数のユニットテストを
@tests/utils/parseDuration.test.ts に追加する。
## 仕様(実装は読まずに、この説明だけからテストを書くこと)
- 入力: "1h30m" のような文字列
- 出力: ミリ秒の数値
- 異常系: 空文字、不正フォーマットは Error を投げる
## 書いてほしい観点(必ず全て入れる)
1. 正常系: "1h", "30m", "1h30m", "2h15m30s"
2. 境界値: "0s"(→ 0), "24h"(→ 86400000)
3. 異常系: 空文字, "abc", "1x", "-1h" → throw
4. 順序入れ替え: "30m1h" は受け付けるか throw か → 仕様確認のうえ実装側に寄せず、私が指示する
## モック方針
- モック禁止(純関数のため)
## 完了条件
- `npm test -- parseDuration` で全ケース Green
- 観点4 については、仕様が曖昧であれば AskUserQuestion で確認してから書く
## 禁止事項
- @src/utils/parseDuration.ts を読まない(実装を見るとトートロジーになる)
- it.skip / test.skip を使わない
- expect(x).toBeDefined() のような弱いアサートで終わらせない
ポイントは**「実装ファイルを読ませない」**ことです。実装を読ませると、Claudeは無意識に実装ロジックをテストへ写経して、バグごと固定してしまいます。仕様だけ渡し、テストが通るかどうかは「実装が正しいかどうか」で決まる構造にします。
Plan Modeで観点を先に洗い出すパターン
仕様が複雑なときは、書く前に観点リストだけPlan Modeで作らせると安定します。
claude --permission-mode plan @docs/spec/parse-duration.md を読んで、テスト観点を「正常系・境界値・異常系・性能」の4分類で
表にまとめて。実装ファイルは読まないこと。
Plan Modeは読み取り系ツールしか動かないので、観点表を作ってもらうフェーズに向いています。観点を確定してからNormal Modeに戻し、テンプレ1の依頼で書かせます。
テンプレ2:既存実装に後追いでテストを足す
レガシーコードへの後追いテスト(特性化テスト)では、目的が「現状の振る舞いを固定する」ことなので、実装を読ませてOKです。ただし、「現状」と「望ましい仕様」を区別させます。
## 目的
@src/legacy/discount.ts の calculateDiscount に特性化テストを追加する。
目的は「現在の振る舞いを固定する」こと(仕様の正しさは問わない)。
## やること
1. @src/legacy/discount.ts を読む
2. 入力の組み合わせを 8 ケース挙げる(条件分岐の網羅、最低でも各 if/else に1つ)
3. 各ケースを実際に1度実行して、戻り値を実測する(Bash で `node -e ...` 等)
4. 実測値を期待値として @tests/legacy/discount.test.ts に書く
5. `npm test` 全体が Green であることを確認する
## 出力
- 8ケースの「入力・実測値・どの分岐を通ったか」の表
## 禁止事項
- 期待値を「こうあるべき」で書かない(必ず実測値を書く)
- 既存挙動が間違っていそうでも、このタスクでは直さない(別タスクに分離)
- 実行できないケース(DB接続が必要など)は省き、その旨を出力に明記する
特性化テストは**「実装が正しい前提」で書くので、ここで後から仕様バグを見つけても同じセッションで直さない**ことが重要です。直したければ別ブランチ・別コミットにします。混ぜると「テストを通すために実装も変えました」が起きて、何が壊れたか追えなくなります。
テンプレ3:モック方針を明示する
「テストを書いて」だけ渡すと、Claudeは安全側に倒して全部モックにしがちです。Mockだらけのテストは「走るが何も保証していない」状態になりやすく、Best Practicesでも公式に「avoid mocks(モックを避ける)」と書かれています。
依頼文では、次の3層をはっきり指示します。
| レイヤー | 推奨スタンス | 理由 |
|---|---|---|
| 純粋ロジック | モックしない | 入出力だけで完結する |
| ファイルI/O・DB | テスト用の実体を使う(一時ファイル、テストDB) | 実体が一番バグを捕まえる |
| 外部API(課金・送信系) | モックする | 課金や副作用が起きる |
モック方針指定の依頼文
## 目的
@src/services/userRepo.ts のテストを @tests/services/userRepo.test.ts に追加する。
## モック方針(最重要)
- DB: テスト用 SQLite を `:memory:` で立てて使う(モック禁止)
- 外部API: 全てモック(課金や送信が起きるため)
- 時刻: vi.setSystemTime で固定(モックOK)
## 観点
- 正常系: 新規作成、更新、削除
- 境界値: 同名ユーザー2件作成(unique制約に当たる)
- 異常系: 接続失敗、トランザクションロールバック
## 完了条件
- `npm test -- userRepo` Green
- `git diff tests/` で「全部モックしただけ」の薄いテストになっていないことを確認
- 各テストが、モック差し替えなしで本物のバグを1つでも捕まえられそうか説明する
最後の「本物のバグを捕まえられそうか説明する」が効きます。Claudeに自分のテストの価値を言葉にさせることで、空のスモークテストを混ぜにくくなります。
テンプレ4:本番バグへの回帰テスト
本番で見つかったバグの再発防止テストは、**「失敗テスト先行」**が必須です。先に直してからテストを書くと、テストが本当にバグを再現できているか分かりません。
## 症状
ユーザーが姓名にスペースを含めると、@src/users/normalize.ts の normalizeName が
" 山田 太郎 " → "山田太郎" になってほしいところを "山田 太郎" のまま返す。
## やること(順序厳守)
1. まず @tests/users/normalize.test.ts に、このバグを再現する失敗テストを1ケース追加する
2. `npm test -- normalize` で Red であることを報告する
3. ここで一度止まる(私の確認待ち)
4. 私が合図したら、@src/users/normalize.ts を最小修正で直す
5. 同じテストが Green になることを確認
## 禁止事項
- 1の段階で「ついでに」修正しない
- 修正は最小範囲。symptom ではなく root cause を直す
- 既存テストの期待値を変えない
「ここで一度止まる」を入れると、AskUserQuestion相当の確認を求めてくれるので、Redが本当にバグを再現しているか人間が見る間が生まれます。バグ修正TDDの詳細はClaude Codeでテスト駆動開発(TDD)を回す手順で扱っています。
AI生成テストの「危ないサイン」チェックリスト
依頼が終わったら、git diff tests/を必ず人間が読みます。次のサインがあれば差し戻します。
-
expect(result).toBeDefined()/toBeTruthy()だけで具体値を見ていない - モックの戻り値そのものをアサートしている(
mockReturnValue(5)→expect(x).toBe(5)) - 同じテストが3回以上、引数だけ変えてコピペされている(
describe.eachに集約できる) - アサートゼロ:実行して例外が出ないことだけを「テスト」と呼んでいる
- テスト名が実装名のコピー:「createUserをテストする」「createUser作成成功」のように、外部から見た振る舞いになっていない
-
it.skip/xit/test.todoが混じっている - 何故その値か説明できない期待値:実装をコピペしただけのトートロジーになっている可能性が高い
/reviewコマンドで、生成テストを別セッションでレビューさせるのも有効です。詳細は同シリーズのClaude Codeをコードレビューに使う方法と人間が見るべきポイントで扱います。
「カバレッジが上がった」を成果指標にしない
AI生成テストでは、カバレッジ(C0/C1)の数値が高いのに、本物のバグを取り逃がすことが頻繁に起きます。理由は単純で、expectが緩いと「行は通ったけれど何も検査していない」状態になるからです。
代わりに、次の指標を依頼文の完了条件に組み込みます。
- 実装側にあえてバグを仕込んだとき、何ケース失敗するか(突然変異テスト的に手で確認)
- 境界値のケース数:if/else の境界1つにつき最低1ケース
- 異常系のケース数:throw する経路ごとに最低1ケース
- モック非依存のケースの数
カバレッジは「100%でも安心しない」の指標として読み、「テストの厚み」は別途人間が判定します。
やってはいけないアンチパターン
- 「全テストを一気に書いて」と頼む:観点が抜ける/薄いテストが量産される。3〜5ケース単位で依頼する
- 実装ファイルを参照させたまま新規テストを書かせる:トートロジーテストの温床
- モック方針を渡さない:全部モックの「実質スモークテスト」になりがち
- テストの修正と実装の修正を同セッションで混ぜる:どちらが原因で緑になったか分からなくなる
expect.any(String)で終わらせる:型チェックなら型システムでやるべき
依頼前の30秒チェックリスト
- 観点(正常系・境界値・異常系・性能)を表で渡したか
- モック方針(純粋ロジックは禁止/DBは実体/課金APIはモック)を書いたか
- テスト対象ファイルとテストファイルパスを
@で参照したか - 特性化テストなら「実測値を期待値にする」と明記したか/新規テストなら「実装ファイルを読まない」と明記したか
- 完了条件に生ログの報告を含めたか(
npm test -- pathの出力)
ここまで揃えれば、Claude Codeは「観点を網羅した、外側から振る舞いを検査するテスト」を書いてくれます。逆に、観点・モック方針・対象範囲のどれかが抜けると、緑だけど何も保証していないテストが返ってきます。AI生成テストを足すこと自体が目的ではなく、「人間がリリース判断できるだけの根拠を増やす」が目的だ、という出発点を毎回確認してください。
次に読む
- Claude Codeでテスト駆動開発(TDD)を回す手順 — Red→Green→Refactorの順序でテスト先行の流れを回す
- Claude Codeの成功率を上げる制約条件と受け入れ条件の作り方 — テストの完了条件と禁止事項の書き分け
- Claude Codeに伝わるプロンプトの基本構造 — 依頼文7ブロックの基本骨格