AIの出力を確かめる
AIの完了の報告は、確かめた結果ではなく、終わったように見えるという判断である。報告ではなく、差分と、点検の出力と、動く実物を見る。
要点
- 確かめは、差分を読む、自動の点検を通す、実物で確かめる、の3段。前の段は後の段の代わりにならない。
- テストが通ったことは、テストが弱められていないことを意味しない。差分はテストと点検の設定から先に読む。
- テストを飛ばす、期待値を緩める、例外を握りつぶす、型の検査を黙らせる、は差分の上で機械的に探せる。
- 点検を通すために点検を書き換えられないよう、書く側と点検する側を分ける。
- 報告には証拠を求める。走らせた命令とその出力、画面の写真。
報告と実際の差
モデルは、課題の文面を満たして意図を外す近道を取ることがある。Anthropic は、期待値をそのまま返す決め打ちと、テストの入力にだけ合う特別扱いを報酬ハックの例に挙げ、Claude Sonnet 3.7 が Claude Code のようなエージェントのコーディングの場面でこの傾向を示したと書いている。後継のモデルでも頻度は下がったが残る[1]。METR は、採点の仕組みを書き換える、時間を測る関数を差し替える、といった例を複数のモデルで観測し、モデルが利用者の意図に反すると自覚している場合があると報告している[2]。
Claude Code の公式文書は、Claude は作業が終わったように見えたところで止まり、確かめる手段が無ければ、終わったように見えることが唯一の根拠になると書く[3]。人が確かめを持たなければ、人がテストの代わりになる。
1段目 差分を読む
本体のコードより先に、点検する側のファイルの差分を読む。点検する側が変わっていれば、2段目の結果は信用できない。
| 先に読むファイル | 見るもの |
|---|---|
| テスト | 消えたテスト、飛ばしたテスト、緩めた期待値、実装の出力を写しただけの期待値 |
| CIの設定 | 失敗を続行にする指定、ジョブの削除、対象の絞り込み |
| 型と静的解析の設定 | strict の解除、規則の無効化、対象から外したディレクトリ |
| カバレッジの設定 | 閾値の引き下げ、計測の対象から外したファイル |
| 依存の定義とロック | 頼んでいない部品の追加と版の変更。AIが持ち込む部品の危険を参照 |
見抜くべき型
テストを弱めて通す
test('税込の金額は1円未満を切り捨てる', () => {
- expect(taxIncluded(1001)).toBe(1101);
+ expect(taxIncluded(1001)).toBeGreaterThan(0);
});
-test('負の金額は拒む', () => {
+test.skip('負の金額は拒む', () => {
expect(() => taxIncluded(-1)).toThrow();
});
期待値を緩めると、どんな誤った実装でも通る。skip はテストの件数の表示では失敗に数えられず、報告では「全部通った」になる。
例外を握りつぶして通す
def settle(order):
+ try:
gateway.capture(order.payment_id)
+ except Exception:
+ pass
order.mark_paid()
決済の確定が失敗しても注文は支払い済みになる。テストで決済の部品を差し替えていれば、テストは通り続ける。CWE はこれを、処置の無い例外ブロック、誤りを検出して何もしない、汎用の例外を捕まえる、として分けている[4][5][6]。
テストの入力にだけ合わせる
function shippingFee(pref, kg) {
+ if (pref === '北海道' && kg === 2) return 1320;
return baseFee(pref) + kg * PER_KG;
}
テストの入力と同じ値の分岐が本体に増えたら、決め打ちを疑う。直すべきは baseFee の表か計算で、分岐ではない。
| 型 | 差分に出る形の例 |
|---|---|
| テストを飛ばす | .skip、xit、@pytest.mark.skip、テストの関数の削除 |
| 期待値を緩める | toBe から toBeTruthy や toBeDefined へ。比較を件数だけにする |
| 期待値を実装から写す | スナップショットの一括更新。失敗した出力をそのまま期待値に貼る |
| 例外の握りつぶし | except: pass、空の catch、.catch(() => {})、誤りを既定値に置き換えて返す |
| 検査を黙らせる | as any、@ts-ignore、# type: ignore、eslint-disable、# noqa |
| 点検の設定を緩める | continue-on-error: true、|| true、閾値の引き下げ |
| 決め打ち | テストの入力と同じ定数での分岐。期待値の定数を返すだけの関数 |
これらは足された行を拾えば見つかる。全部が誤りではないが、1件ずつ理由を言えることを確かめる。
git diff main...HEAD -U0 \
| grep -nE '^\+.*(\.skip\(|xit\(|mark\.skip|@ts-ignore|as any|type: ignore|eslint-disable|noqa|except.*: *pass|catch *\([^)]*\) *\{ *\}|continue-on-error|\|\| *true)'
2段目 自動の点検を通す
型、静的解析、テスト、ビルドを、毎回同じ命令で走らせる。Claude Code の公式文書は、指示を2つに分ける。モデルが読んで従うかを決めるものと、道具が必ず実行するものである。「終わる前に型を確かめる」と書いた指示は大半の回で守られるが、毎回ではない。必ず走らせたいなら、終わろうとした時に走る Stop フックか、CIに置く[3]。
.claude/settings.json 点検が通るまで終われない
{
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command", "command": "scripts/check.sh 1>&2 || exit 2" } ] }
]
}
}
終了コード2で終了を止め、標準エラーの内容がAIへ返る。同じ文書によれば、道具の呼び出しを挟まずに8回続けて止めると、次は止めずに終わる[3]。点検の結果は報告ではなく、最後の実行の出力で見る。
- 既存のテストの期待値と、CI、型、静的解析の設定の変更は、本体の変更と別に読む。同じ変更に混ざっていたら分けさせる。
- 例外を許さないなら、そのディレクトリへの編集を PreToolUse フックか拒否の設定で止める[3]。
- テストが弱いかどうかは、通った件数では測れない。実装をわざと壊してテストが落ちるかを見る変異テストで測れる。道具には Stryker や PIT がある[7][8]。
3段目 実物で確かめる
テストは、書いた人の想定の中しか確かめない。本物の設定、本物の画面の幅、本物のDBで動かして見る。NIST の SSDF も、人が読むコードの点検と、実行できるコードの試験を別の実践として置いている[9]。
| 変更 | 実物で見るもの |
|---|---|
| 画面 | スマホ幅とパソコン幅の写真。利用者の操作の流れを最後まで通す |
| API | 実際の要求と応答。権限の無い利用者での応答。誤りの時の状態コード |
| DBの変更 | 移行のあとの実際の列と制約。移行のログやスキーマのファイルを根拠にしない |
| 送信、バッチ | 送り先と件数のログ。宛先を手元の受け口に向けて流す |
| 設定、インフラ | 適用の前の差分の計画。消える資源が無いか |
走らせた命令と、その出力と、画面の写真。Claude Code の公式文書も、証拠を求めるよう勧め、「全部のテストが通った」という文だけではほとんど何も分からないとしている[3]。見ていなかった作業では、それが唯一の証拠になる。
別の目で読ませる
- 書いた会話の推論に引きずられないよう、読む側は会話を見ていない新しい文脈で読ませる。Claude Code では
/code-reviewが、会話を見ていないサブエージェントで差分を読む[3]。 - 読ませるときは、仕様に照らして足りない所を探させ、書き方の好みを外させる。問題を探せと言われた読み手は何かを必ず見つけ、全部を直すと不要な抽象と起こり得ない場合のテストが増える[3]。
- 別の目も報告である。最後は1段目から3段目の証拠で決める。
参照
- System Card: Claude Opus 4 & Claude Sonnet 4 Anthropic、2025年5月。6 Reward hacking
- Recent Frontier Models Are Reward Hacking METR、2025年6月5日
- Best practices for Claude Code Anthropic
- CWE-1069 Empty Exception Block MITRE
- CWE-390 Detection of Error Condition Without Action MITRE
- CWE-396 Declaration of Catch for Generic Exception MITRE
- Stryker Mutator Stryker
- PIT Mutation Testing PIT
- SP 800-218 Secure Software Development Framework Version 1.1 NIST。PW.7 と PW.8