Claude Codeテスト生成は『仕様先行』で変わる──論文根拠と実践手順

Claude Code にテストコードを書かせているのに、どこか信用しきれない。生成されたテストは全部グリーンなのに、本番で普通にバグが出る。実装を読ませて「テスト書いて」と頼むと、今の実装をなぞっただけの「通るためのテスト」が量産される。そんな経験がある現役エンジニアは多いはずです。

2026年8月21日、Google の研究者らによるものとして「テスト生成の前に、対象コードの事前条件・事後条件・未検証の挙動を自然言語で書き出させるだけでバグ発見率が大きく上がった」という報告が X で拡散しました。この報告の元論文は後述の通り僕には特定できていないのですが、ポイントはテストを書く腕前ではなく、その前段の「コードが何をすべきかを言葉にする」工程だった、という点です。そしてこの構造自体は、別の論文2本が数値付きで裏付けています。

この記事では、その手順を Claude Code の運用に落とし込みます。仕様書を先に書かせるプロンプトの型、生成物の読み方、CLAUDE.md や Hooks で「仕様なしのテスト生成」を止める運用ルール、そして現場で感じた限界までを扱います。対象は Claude Code や Cursor などのコーディングエージェントを日常的に使っている現役エンジニアです。読み終える頃には、明日のテスト生成プロンプトを「まず仕様から」に変えるための具体的な手順とテンプレートが手元に残ります。僕自身、2019年にテストも仕様もないまま納期当日を迎えた失敗があり、その反省をそのまま AI 相手に適用している話も書きます。

背景・課題 ── Claude Code テスト生成が「通るだけ」になりやすい理由

X で拡散した報告の要旨と、一次ソースの確認状況

発端は AI 論文解説メディア AIDB の X アカウント(@ai_database)が2026年8月21日に投稿したポストです。要旨は次の通りでした。

  • テストコードをいきなり書かせるのではなく「まず仕様書を書け」と指示するだけで、AI のバグ発見率が大きく上がった
  • Google の研究者らによる報告で、実験モデルは Gemini 3 Flash
  • 手順は、実装を読ませて「実行前に満たすべき条件」「実行後に保証される結果」「まだ検証されていない挙動」を文章で書かせ、それを根拠にテストを生成する

投稿は表示3.9万・ブックマーク428件と、保存されやすい内容として日本語圏で広がりました。

ただし正直に書いておくと、執筆時点で僕はこの報告の元になった論文本体(タイトル・著者・公開日)を arXiv や Google の技術ブログから特定できていません。そのため、投稿に含まれていた「人間のテストと比較した割合」などの数値はこの記事では引用しません。上位モデルで同じ上乗せが出るかも未検証です。

一方で、「実装を見せてテストを書かせると実装追認になる」「仕様を先に書かせると改善する」という構造自体は、同時期に公開された別の研究で数値付きで確認できます。

「実装追認テスト」が生まれる構造は、別の論文が数値で示している

トロント大学の Junda Zhao らが2026年7月24日に公開した Evaluating and Mitigating the Misguidance Effect of Buggy Code in LLM-Generated Unit Tests は、まさにこの現象を「misguidance effect(誤導効果)」と名付けて測定しています。

  • バグを含む実装をプロンプトに入れると、LLM はそのバグ挙動を正しいものとして assert するテストを生成しやすくなる
  • 対策として、プロンプト内の実装コードを LLM 生成の仕様 docstring に置き換える「specification-based」方式を提案
  • Defects4J の実バグ 233 件・318 メソッドで、Gemini 2.5 Pro / Flash、Claude 4 Sonnet、GPT-4.1 など 11 モデルを評価
  • 誤導テストの割合は平均 3.84% から 2.69% に減り、バグを検出できる有効テストは 2.98% から 4.50% に増えた

もう1本、Amin Haeri らが2026年7月7日に公開した Specification Grounding Drives Test Effectiveness for LLM Code では、仕様を文章で与えた場合は 30 件中 27 件のバグを検出できたのに対し、仕様なしでテスト計画を立てさせた場合は 30 件中 2 件しか検出できなかったと報告されています。誤検知率も 33% から 0% に下がりました。こちらは Claude Haiku 4.5 / Sonnet 4.6 / Opus 4.8、GPT-5.3-codex、Gemini 3.5 Flash と複数ベンダーで再現しています。

つまり「実装をコンテキストに入れた状態でテストを書かせると、期待値が実装から逆算される」のは、モデルの性格ではなく構造の問題です。実装が正しければ問題は起きませんが、実装がバグっているときほどテストも一緒にバグる。これが「全部グリーンなのに本番でバグが出る」の正体です。

2019年、仕様もテストもなく納期当日を迎えた

この構造の話を読んだとき、僕が思い出したのは 2019年8〜9月の出来事でした。

エンジニアに転職してまだ2ヶ月ほどの頃、電気の新規契約フォームを任されました。納期当日になっても、バリデーションは未実装、入力不備の表示機能もなし、テストも実施できていない。プロジェクトマネージャーにあたる人は長期休暇で連絡が取れず、先方は納期を把握していて遅い時間まで納品を待っている状態でした。一人では何もできないので、20歳年下の先輩を無理やり引き込み、深夜3時にもお客さんと電話で状況を連絡しながら実装を進め、完全ではないが「それっぽいもの」を納品したのが朝6時頃。そのまま先輩と一緒に帰りました。

納品後は修正が大量に発生しました。8割の完成度にするには2割の工数で済むのに、残り2割を作り切るのに8割のパワーがかかる。80:20 の法則をエンジニア業務で身をもって体験したのがこのときです。

いま振り返ると、根本原因はテストを書く腕前ではありませんでした。「このフォームが何を満たすべきか」、つまり必須項目は何か、各項目の形式は何か、不備があったときにどう振る舞うべきか。それを誰も言語化していなかった。だから何を検証すればいいかも決まらず、納品後に「未検証の挙動」がそのまま修正依頼として返ってきた。冒頭の報告が「大事なのはテストを書く腕前ではなく、コードが何をすべきかを言葉にする段階」だと言っているのと同じ話だと思います。

2026年の今、僕は Claude Code に対して同じ教訓を使っています。ゴールが曖昧なまま投げると、人に説明して突っ込まれるのと同じ箇所で Claude にも突っ込まれる。だから先に壁打ちしてゴールを言語化してから実装を渡す。2025年3月から取り組んだ OCR×LLM の請求書読み取り改善でも、従来のラベリング型 OCR で正解率が約1割、という数字を出すには、先に「この請求書のどの値が正解か」を決めておく必要がありました。正解を決めて精度を測っていたからこそ、Azure OCR と OpenAI の LLM の二段階方式に切り替えたときの改善幅が見えた。仕様先行は、7年前に人間相手に学んだことが AI 相手のテスト生成でもそのまま成立している、という感覚です。

実践手順 ── Claude Code で「仕様→テスト」の順に書かせる3ステップ

ここからは、その手順を Claude Code に落とし込みます。前提として Claude Code は Pro プラン(月額 $20)以上で利用でき、Max プランは月額 $100 からです。以下のプロンプトは Cursor や Codex でもほぼそのまま使えます。

Step 1 実装を読ませて、仕様ファイルを先に書かせる

対象ファイルを読ませ、docs/specs/<module>.md に「事前条件 / 事後条件 / 未検証の挙動」を日本語の箇条書きで出力させます。ポイントは「実装の詳細ではなく、何をすべきかで書く」と明示することと、実装を正解扱いさせないことです。

src/billing/calculate_invoice.py を読み、docs/specs/calculate_invoice.md に
以下の3節を日本語の箇条書きで書いてください。
 
## 事前条件
この関数が呼ばれる前に満たされているべき入力・状態
 
## 事後条件
この関数が正常終了したときに保証されるべき結果・副作用
 
## 未検証の挙動
既存テスト tests/test_calculate_invoice.py で検証されていない入力・境界・異常系
 
制約
- 「何をすべきか」で書く。実装がどう動いているかの説明は書かない
- 実装にバグが含まれている可能性を前提にする。実装の挙動をそのまま仕様にしない
- 現状の実装と仕様が食い違うと思われる箇所は「要確認」と明記する
- テストコードはまだ書かない

「実装をそのまま仕様にしない」「要確認を明記する」の2行が、誤導効果を抑えるための肝です。この段階の出力は人間が必ず読みます。特に「要確認」と「未検証の挙動」の欄は、ここで人間の知識を足すと後工程の精度が大きく変わります。

Step 2 仕様ファイルだけを根拠にテストを生成させる

次に、仕様ファイルを根拠にテストを書かせます。「未検証の挙動」を優先してテストケース化させるのがポイントです。

docs/specs/calculate_invoice.md を読み、tests/test_calculate_invoice.py に
テストを追加してください。
 
優先順位
1. 「未検証の挙動」に列挙された項目を1件ずつテストケースにする
2. 「事後条件」を assert で表現する
3. 「事前条件」に違反する入力に対する挙動をテストする
 
制約
- 期待値は仕様ファイルの記述から決める。実装の戻り値を見て期待値を合わせない
- 仕様に書かれていない期待値を追加しない。必要なら仕様ファイルへの追記を提案する
- 各テスト関数の docstring に、対応する仕様の項目名を書く

「実装の戻り値を見て期待値を合わせない」を入れておくと、テストが落ちたときに「実装が間違っているのか、仕様が間違っているのか」を人間が判断できる状態で止まってくれます。

Step 3 生成されたテストをレビューする観点

生成物は次の3点で読みます。

  1. 仕様にない期待値が混ざっていないか。混ざっていれば、それは実装から逆算された値の疑いがある
  2. 「未検証の挙動」が何件テストになったか。Step 1 で 8 件挙がったのに 3 件しかテスト化されていなければ、残り 5 件は明示的に追加を頼む
  3. 落ちたテストを「実装のバグ」か「仕様の誤り」かに仕分けする。仕様側が誤りなら仕様ファイルを直してから Step 2 をやり直す

いきなりテスト生成と仕様先行の比較

| 観点 | いきなりテスト生成 | 仕様先行(Step 1→2) | |---|---|---| | テストの根拠 | 実装コード | 仕様ファイル(人間がレビュー済み) | | 実装追認リスク | 高い。バグ挙動がそのまま assert される | 低い。仕様と実装の食い違いがテスト失敗として出る | | 未検証挙動の可視化 | されない | Step 1 で一覧化される | | トークン消費 | 1往復 | 2往復以上。仕様の読み書き分が増える | | 所要時間 | 短い | 仕様レビューの時間が加わる | | レビューのしやすさ | テストコードを1件ずつ読む必要がある | 仕様ファイルを読めば網羅範囲がわかる | | モデル依存 | モデルの推論力に依存 | 小さいモデルでも効きやすい(研究は Flash 級・2.5 世代で確認) |

コストが上がるのは事実なので、後述の通り「どの粒度で使うか」の線引きは必要です。

運用に組み込む ── CLAUDE.md・Skill・Hooks

個人のプロンプトで止めず、チームのリポジトリに組み込むには3段階あります。

CLAUDE.md に1行入れる

公式ドキュメントによると、CLAUDE.md はプロジェクトルートの ./CLAUDE.md に置き、200行以内の具体的なルールとして書くのが推奨されています。

## テスト生成ルール
- テストコードを追加・変更する前に、対象モジュールの docs/specs/<module>.md を作成または更新すること。仕様ファイルが無い状態でテストを書かない

ただし公式ドキュメントにも明記されている通り、CLAUDE.md は「コンテキスト」であって強制ではありません。確実に止めたいなら Hook を使います。

Skill 化してスラッシュコマンドにする

.claude/skills/spec-test/SKILL.md を作ると /spec-test で呼び出せます。Skills のドキュメントによれば、disable-model-invocation: true を付けると Claude が勝手に発動せず手動起動専用になり、引数は $ARGUMENTS で受け取れます。

---
name: spec-test
description: 対象ファイルの仕様ファイルを先に作り、それを根拠にテストを生成する
disable-model-invocation: true
argument-hint: [対象ファイルのパス]
---
 
対象ファイル $ARGUMENTS について、次の順で作業してください。
 
1. 実装を読み、docs/specs/<module>.md に「事前条件 / 事後条件 / 未検証の挙動」を日本語の箇条書きで書く。実装の挙動をそのまま仕様にせず、疑わしい箇所は「要確認」と書く
2. ここで一度止まり、仕様ファイルのレビューを求める
3. レビュー後、仕様ファイルだけを根拠にテストを追加する。「未検証の挙動」を優先する

PreToolUse Hook で仕様なしのテスト書き込みを止める

Hooks のドキュメントによると、PreToolUsesettings.jsonmatcher にツール名(Write|Edit など)を指定でき、フックスクリプトは stdin で tool_input.file_path を含む JSON を受け取ります。終了コード 2 を返すとツール呼び出しがブロックされます。JSON を返さない場合は stderr の内容がブロック理由としてモデルに渡されます。下のスクリプトはこの stderr 方式です。

.claude/settings.json に次を追加します。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/require-spec.sh"
          }
        ]
      }
    ]
  }
}

.claude/hooks/require-spec.sh は次の通りです(jq が必要です)。

#!/bin/bash
input=$(cat)
# file_path は Claude Code が渡す絶対パス前提
path=$(jq -r '.tool_input.file_path // empty' <<<"$input")
 
# テストファイル以外は素通し
case "$path" in
  *_test.*|*.test.*|*/tests/*|*/test_*) ;;
  *) exit 0 ;;
esac
 
# tests/test_calculate_invoice.py → docs/specs/calculate_invoice.md
module=$(basename "$path")
module=${module#test_}
module=${module%%.*}
module=${module%_test}
module=${module%.test}
spec="docs/specs/${module}.md"
 
if [ ! -f "$spec" ]; then
  echo "仕様ファイル ${spec} が存在しません。先に /spec-test で仕様を作成してください" >&2
  exit 2
fi
exit 0

これで、仕様ファイルが無いままテストファイルに書き込もうとすると Claude Code 側でブロックされ、stderr に書いた理由がモデルに返されます。chmod +x .claude/hooks/require-spec.sh を忘れずに。

自分のリポジトリで今日試す手順

  1. 直近で触っているモジュールを1つ選ぶ(既存テストがあるものが望ましい)
  2. Step 1 のプロンプトで docs/specs/<module>.md を作らせる
  3. 「未検証の挙動」欄を既存テストと突き合わせ、本当に未検証の項目が何件あるかを数える
  4. 「要確認」項目に自分の知識を書き足す
  5. Step 2 のプロンプトでテストを追加させ、落ちたテストを「実装のバグ / 仕様の誤り」に仕分けする
  6. 効果があると感じたら CLAUDE.md に1行足し、チームで合意できたら Hook を入れる

僕自身はこの手順を業務コードで運用し始めたところで、バグ発見率の数値比較はまだ取れていません。なので効果量はここで断言せず、手順として提示するに留めます。

課題・限界 ── 仕様先行が効かないケース

ここまで推してきましたが、現場で感じている限界も書いておきます。

仕様も AI に書かせると「現状実装の言い換え」になる罠

Step 1 で「実装をそのまま仕様にしない」と制約を入れても、バグを含む実装から起こした仕様にはバグが混ざります。Zhao らの論文でも、仕様 docstring の段階でバグを正しく修正できていた割合は Gemini で 62.26%、Qwen で 47.48% でした。つまり4〜5割は仕様の時点でバグが残る。仕様の一部は人間が書く、あるいは最低でも「要確認」欄を人間がレビューする工程を抜くと、この手法は効きません。

二段階になるぶんトークンと時間が増える

小さなユーティリティ関数に毎回仕様ファイルを作るのは過剰です。僕の線引きは「外部入力を受ける」「金額や日付など業務ルールを持つ」「既存テストが実装追認っぽい」のどれかに当てはまるモジュールに限定する、というものです。

上位モデルでは効果が縮む可能性

拡散した報告の実験モデルは Gemini 3 Flash、Zhao らの評価は 2.5 世代が中心です。Opus 系のような上位モデルは内部で同様の整理をしている可能性があり、上乗せ幅は小さいかもしれません。Haeri らの論文では Opus 4.8 でも改善が出ていますが、幅はモデルによって異なります。

「未検証の挙動」欄の質がすべて

仕様がバグ挙動を捉えていなければ、その仕様から生成したテストでもバグは見つかりません。効くかどうかは「未検証の挙動」欄にどれだけ本当の境界条件が書けたかで決まります。

テストが増えるほど「安全」の錯覚が起きる

テスト生成が楽になるほど、テストの本数やカバレッジで安心しがちです。見るべきは行カバレッジではなく仕様の網羅率、つまり「仕様ファイルの項目のうち何件がテストになったか」です。

これらの限界を踏まえた上で、僕は「大きめの業務ロジックでは仕様先行、小さな関数は従来通り」という使い分けで活用しています。

まとめ

  • AI テストの質は、テストを書く腕前より「コードが何をすべきか」の言語化で決まる。実装を見せてテストを書かせると期待値が実装から逆算される
  • Claude Code には「仕様ファイル → テスト」の順で頼む。Step 1 で事前条件・事後条件・未検証の挙動を書かせ、Step 2 で仕様だけを根拠にテストを書かせる
  • 「未検証の挙動」を先に洗い出し、人間が「要確認」欄をレビューする工程を抜かない
  • CLAUDE.md で方針を示し、Skill で手順を固定し、PreToolUse Hook で仕様なしのテスト書き込みを止める
  • 上位モデルや小さな関数では過剰になりうる。効果量は自分のリポジトリで測る

次のアクションは1つだけです。今日、直近で触っているモジュールを1つ選び、Step 1 のプロンプトで仕様ファイルを作ってみてください。そのうえで既存テストと突き合わせ、「未検証の挙動」が何件あるかを数える。その件数が、今のテストがどれだけ「通るだけ」だったかの答えになります。

参考リンク