Claude Media
Claudeがテストだけ通すハードコードを防ぐプロンプトの書き方

Claudeがテストだけ通すハードコードを防ぐプロンプトの書き方

Claudeがテストケースの値だけに反応する分岐を書いてしまう問題を、公式ドキュメントの文言でどう防ぐかをまとめます。CLAUDE.mdへの落とし込み方も扱います。

Claudeがテストだけ通すハードコードを書いてしまう理由

Claudeはテストを通すことを優先しすぎると、対象のロジックそのものではなく、個々のテストケースの入力値だけに反応するコードを書くことがあります。公式ドキュメントはこの傾向を「テストを通すことに重点を置き過ぎて、汎用的な解決策を犠牲にすることがある」と明記しています。複雑なリファクタリングでは、標準的なツールを直接使わずヘルパースクリプトで回り道することもあるそうです。

「入力が5のとき10を返す関数を実装して」というテストが1件しかない状態で実装を頼むと、if (x === 5) return 10; のような分岐だけで済ませてしまう余地が生まれます。このコードはテストを通しますが、入力が6のときの挙動は何も保証されません。

テストを2件に増やしても、構造は変わりません。「5のとき10、7のとき14」なら、if (x === 5) return 10; if (x === 7) return 14; と分岐を2つ積めば両方を通せます。実装側が一般式(x * 2)ではなく個別の対応表を書く余地は、テストが何件あっても残ります。

だから対処は2層になります。1層目は、個別の対応ではなく一般式を書くようプロンプトで明示すること。2層目は、プロンプトに書いていない入力でも通るかを、Claudeの外側で検査することです。

公式が推奨するプロンプトの文言

1層目の中身は、テストの役割そのものを明文化した指示です。公式ドキュメントは次の文言をそのまま使える例として挙げています。

Please write a high-quality, general-purpose solution using the standard tools
available. Do not create helper scripts or workarounds to accomplish the task more
efficiently. Implement a solution that works correctly for all valid inputs, not just
the test cases. Do not hard-code values or create solutions that only work for specific
test inputs. Instead, implement the actual logic that solves the problem generally.
 
Focus on understanding the problem requirements and implementing the correct algorithm.
Tests are there to verify correctness, not to define the solution. Provide a principled
implementation that follows best practices and software design principles.
 
If the task is unreasonable or infeasible, or if any of the tests are incorrect, please
inform me rather than working around them. The solution should be robust, maintainable,
and extendable.

ポイントは3つです。1つ目は、標準ツールの使用を求め、ヘルパースクリプトによる回り道を塞ぐこと。2つ目は、「テストは正しさを検証するものであり、解を定義するものではない」とテストの役割を明言すること。3つ目は、テストや要件自体が不合理なときは回避せず報告するよう求めることです。3つ目を入れておくと、テストの前提が壊れているケースまでハードコードで押し切られる事故を防げます。

2つ目のように禁止だけでなく理由まで添える書き方は、プロンプトガイドでも一般原則として挙げられています。動機を説明すると、Claudeがその説明から一般化して意図をつかみやすくなるためです。原則そのものはClaudeプロンプトの書き方 — 良い指示を出す7つのコツにまとめています。

文言だけでは足りない場面: 見せていない入力で検査する

文言を渡しても、複雑な実装ではハードコードが紛れ込むことがあります。そこで、Claudeに見せたテストとは別の入力で実装を叩く検査を、Claudeの外側に置きます。効き方は、最小の構成で比べると分かります。

src/double.js は入力を2倍にする関数で、ここではわざとハードコードで書いてあります。

export function double(x) {
  if (x === 5) return 10;
  if (x === 7) return 14;
  return 0;
}

プロンプトで見せたテストは 5 → 10 と 7 → 14 の2件です。これとは別に、-3、0、1、6、100、12345 の6つの入力で x * 2 と一致するかを見る検査用テストを test/heldout.test.js に置きます。Node.js標準のテストランナー(node --test)で両方を実行した結果は次のとおりです。

数字

同じ実装に2種類のテストを当てた結果

  • 見せたテスト(2件)

    2件通過

    # pass 2 / # fail 0

  • 見せていない入力の検査(1件)

    1件失敗

    期待値 -6 に対して実際は 0(最初に落ちた入力は -3)

Node.js v20 の node --test。実装はハードコード版

見せたテストだけを見ていれば、この実装は完成に見えます。検査用テストが落ちて初めて、return 0 の手抜きが露出します。失敗メッセージに期待値と実際の値が出るので、Claudeに戻すときの修正指示としてもそのまま使えます。

検査用テストを置くときの注意

検査用テストが実装と同じリポジトリにあれば、Claudeから読める位置にあります。読まれて困るのは、入力が固定の列挙だけの場合です。今回の6つの入力も、対応表にもう6行足せば通せてしまいます。

そこで、検査は性質で書くのが向いています。「任意の整数xで double(x) は x * 2 に等しい」のように範囲全体を問う形にし、可能なら入力を実行ごとに変えると、個別の対応表で通す余地が減ります。テスト自体が弱くないかを測る手段は、Claude CodeでStrykerのミューテーションテストを回し弱いテストを補強するで扱っています。

Stop hookに繋ぐと、落ちたときに作業へ差し戻せる

検査を手で回す代わりに、Claudeが応答を終えようとする瞬間に自動で走らせることもできます。公式のベストプラクティスは、確認手段を働かせる強さとして、1回のプロンプト内・/goal 条件・Stop hook・別のサブエージェントによる再確認の4段階を挙げています。Stop hookは、検査をスクリプトとして実行し、落ちたら終了をブロックする決定論的な関門です。

次の check.sh は、作業ディレクトリで検査を実行し、落ちたら終了コード2で止めます。hookが終了コード2を返すと、標準エラー出力のメッセージがClaudeに渡され、作業が続きます。

#!/bin/sh
input=$(cat)
case "$input" in *'"stop_hook_active": true'*|*'"stop_hook_active":true'*) exit 0 ;; esac
if ! out=$(node --test test/ 2>&1); then
  echo "テストが落ちています。入力値ごとの分岐ではなく一般式で実装し直してください。" >&2
  echo "$out" | grep -E "^# (tests|pass|fail)" >&2
  exit 2
fi

2行目の stop_hook_active の判定は、無限ループよけです。hookの入力に含まれるこのフィールドは、hookによる続行の最中に true になります。Stop hookが続行させたターンが8回連続すると、次のブロックは上書きされてターンが終わります。上限は環境変数 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP で変えられます。この check.sh は2回目の判定を通すので、差し戻しは1回です。通るまで続けさせたい場合は、2行目の判定を外して上限の8回に任せる構成もあります。

手順

Stop hookで弾く構成を組む

  1. 1

    検査用テストを置く

    見せたテストとは別に、範囲全体を問う検査を test/ の下に置きます。ハードコードだと落ちるものを選びます。

  2. 2

    check.sh を実行可能にする

    chmod +x check.sh を実行します。

  3. 3

    settings.json に登録する

    .claude/settings.json の hooks に Stop を追加し、command で check.sh を指します。パスに ${CLAUDE_PROJECT_DIR} を使えば、作業ディレクトリに依存しません。

登録の書式は次のとおりです。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/check.sh" }
        ]
      }
    ]
  }
}

check.sh 単体の動きは、疑似的なhook入力を標準入力に流して確かめられます。ハードコード版では終了コード2とともに、# tests 3、# pass 2、# fail 1 の3行が標準エラー出力に出ました。stop_hook_active を true にした入力では、同じ状態でも終了コード0で通り抜けます。1回目は差し戻し、2回目は通す設計です。

Stop hookの代わりに、/goal で「node --test test/ が成功すること」を条件にする方法もあります。/goal は、毎ターンの後に別のモデルが条件の成否を判定する仕組みです。スクリプトで機械的に判定したいか、モデルに判断させたいかで選びます。PostToolUseとStopの使い分けはClaude Code hooksでテストを自動実行するで比較しています。

ヘルパースクリプトによる回り道も合わせて防ぐ

最初の文言にも「ヘルパースクリプトを作らない」という指示は含まれています。それでも回り道を完全に塞ぎたい場合は、一時ファイルの扱いを定めた別の文言を併用できます。公式ドキュメントは、複雑な作業の途中でPythonスクリプトなどを「一時的な作業場」として作ること自体は成果を高める場合があると認めつつ、新規ファイルの増加を抑えたいなら後片付けを求める文言を挙げています。

If you create any temporary new files, scripts, or helper files for iteration, clean up
these files by removing them at the end of the task.

この文言は「ヘルパースクリプトを作るな」ではなく「作ってもよいが最後に消せ」という指示です。ハードコードを防ぐ文言と役割が違うので、置き換えではなく併用します。実装の探索に一時スクリプトを使うこと自体は妨げず、成果物に回り道の跡だけが残らないようにする切り分けです。

CLAUDE.mdか、その場の指示か

この文言は2箇所のどちらかに置けます。1回限りの依頼ならプロンプトにそのまま貼り、繰り返す作業なら短く要約してCLAUDE.mdに置きます。CLAUDE.mdは会話の開始時に毎回読み込まれるファイルで、Claudeがコードから推測できない前提や規約を置く場所です。

# 実装方針
- テストケースの値に対する分岐ではなく、要件を満たす汎用ロジックを書く
- ヘルパースクリプトでの回避をせず、標準ツールを使う
- テストや要件が不合理なら報告し、ハードコードで押し切らない

公式のベストプラクティスは、CLAUDE.mdが長すぎると重要なルールがノイズに埋もれて無視されやすくなると警告しています。指示がなくてもClaudeが正しく動けているなら削るか、hookに変えるよう勧める内容です。例外なく毎回守らせたいことは、CLAUDE.mdの助言に頼らず、前節のStop hookのような決定論的な仕組みに寄せます。テスト駆動開発の進め方とCLAUDE.mdへの書き方の切り分けはClaude CodeでTDDを回す手順にまとめています。

テストをClaudeに書かせる場合の指示

Claude自身にテストを書かせる場面では、テストの作りが緩いと、ハードコードでも通る実装が許されてしまいます。境界値や異常系を含む複数のテストを用意させ、1件だけのテストで実装を任せない運用を組み合わせると、個別の対応表で通せる余地が狭まります。テストを書かせる指示にも、件数と種類を条件として添えます。

テストは最低4件用意してください。通常値1件、境界値(0と最大値)2件、
異常系(不正な入力)1件を含めること。特定の入力値に対する分岐で
通せてしまう実装を弾けるだけの多様性を持たせてください。

種類を先に指定すると、テストが1〜2件の通常値だけに偏る事態を避けられます。

Playwrightでのエンドツーエンドテストのように、画面の見た目まで検証対象になる場面でも、特定のシナリオの画面遷移だけを再現するコードが書かれるリスクは同じ構造で起きます。テストの自動実行をhookに組み込む手順はClaude CodeでPlaywrightのE2Eテストをhookで自動実行するにまとめています。しきい値の判定のように境界値の扱いを個別に書き分けやすい場面の対策は、Claude Codeでk6の負荷テストスクリプトを作成する手順を参照してください。

似ているが別の問題と混同しない

「テストだけ通すハードコード」と紛れやすい問題が2つあります。区別しておくと、対処するプロンプトを取り違えずに済みます。

問題内容対処するプロンプトの要点
テストのみ通すハードコード内容テストケースの入力値だけに反応する分岐を書く対処するプロンプトの要点この記事の文言(汎用ロジックを要求)
オーバーエンジニアリング内容頼まれていない抽象化・設定可能性・防御的コードを増やす対処するプロンプトの要点依頼された変更だけに留めるよう要求する別文言
ハルシネーション内容開いていないファイルの内容を推測して答える対処するプロンプトの要点ファイルを読むまで推測しないよう要求する別文言(Never speculate about code you have not opened. など)

オーバーエンジニアリングは、公式ドキュメントがClaude Opus 4.5とClaude Opus 4.6の傾向として明記している問題です。頼まれていない拡張を増やす方向の失敗で、入力値だけに反応する方向の失敗とは逆向きです。片方を防ぐ指示だけでは、もう片方は防げません。両方が気になる場合は、それぞれの文言を別々に渡します。同じプロンプトに両方を入れるなら、「必要な処理は省略せず書くが、頼まれていない拡張は追加しない」という切り分けを添えておくと、指示同士がぶつかりにくくなります。

それでも起きたときの見つけ方

プロンプトとStop hookを入れていても、レビューで確かめられる箇所があります。次の3つです。

  • 実装の分岐条件に、テストファイルに出てくる具体的な数値や文字列がそのまま使われていないか
  • テストケースの件数と、実装内の条件分岐の数が近すぎないか(あくまで目安として、1対1に近いほど疑わしくなります)
  • テストを1件追加しただけで、実装側の分岐も1つ増える構造になっていないか

テストを書いたセッションとは別の文脈で実装させると、実装側が自分の書いたテストの意図に引きずられません。ベストプラクティスは、新しい文脈のほうがClaudeが直前に書いたコードに偏らないとして、コードレビューを別セッションに任せる進め方を挙げています。テストについても、一方のClaudeにテストを書かせ、別のClaudeにそれを通すコードを書かせる形が示されています。文脈を分けて実装させる手順はClaude CodeでTDDを回す手順で扱っています。

疑わしい実装が見つかったら、別の文脈でその1点だけを確認させる方法もあります。

src/calc.ts の実装を確認してください。テストファイルに出てくる
入力値や期待値がそのままハードコードされている箇所がないか、
一般化されたロジックになっているかだけを見てください。

対象を「ハードコードの有無」1点に絞ると、無関係な指摘まで返ってくる事態を避けられます。

よくある質問

Claude Codeでなく、Claude APIやAgent SDKでも使えますか

使えます。この文言はモデルへの指示として渡す普通の文章で、Claude Code固有の機能に依存していません。APIならシステムプロンプトかユーザープロンプトに、Agent SDKで組んだエージェントなら同じ位置に渡せば、Claude Codeでの使い方と変わりません。Stop hookによる検査はClaude Codeのhook機能なので、API直接利用では、自分のテスト実行ループに同じ検査を組み込む形になります。

テストが用意できない作業でも意味がありますか

「テストのみ通すハードコードを防ぐ」という狙いそのものは、テストが存在しない場には働きません。ただし同じプロンプトに含まれる「標準ツールを使い、一般解を実装する」という部分は、テストの有無に関係なく効きます。小さな修正でテストを用意する時間が取れないときは、この部分だけを渡す使い方も成立します。

まとめ

プロンプトだけで防ぎきろうとせず、「一般式で書く」指示と、見せていない入力での検査の2層で組みます。検査まで入れるなら、落ちたら差し戻すStop hookが一番手間の少ない置き場所です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompt…
  2. code.claude.com/docs/en/best-practices
  3. code.claude.com/docs/en/hooks
  4. code.claude.com/docs/en/goal
この記事を共有:XはてブLinkedIn