AI が書いたテストの緑は信じていい? 15回ともバグの値で緑だった:Claude Code と TDD で3通りの頼み方を比べる
Claude Code にテストを書かせると全部緑で返ってきて、そのまま信じていいか迷うことがあります。
同じバグ入りの関数で試すと、実装から書かせたテストは15回ともバグの値で緑、人の受入表から書かせたテストは15回ともそのバグを赤にしました。
今日やるのは1つ、AI のテストの期待値の数字を、人が書いた仕様や受入表と照らすことです。
この下には、3通りの頼み方の全文、3モデル×5回の内訳、手元で試す手順、出典があります。
!
この記事の内容は動画でも解説しています。③で Claude Code の入力欄に受入表を渡す画面から、REQ-003 の赤が出るまで(6:30〜)を、約10分の動画で追えます。
動画は 10/27 07:00 公開予定です → youtu.be
結果:実装から書かせたテストだけが、15回ともバグの値で緑
試したこと:同じバグに、3通りの頼み方
期待値は、テストが「正しい答え」とする値です。expect(calc(1000)).toBe(1000) なら、toBe に渡した 1000 が期待値です。期待値そのものが間違っていたら、テストが緑でも何も確かめていません。
材料は、割引コードを使った支払額を返す関数です。バグをわざと1か所だけ入れました。2つのコードを併用すると、両方を順に引いてしまいます。
src/cart.js
// 割引コードを適用した支払額(円)を返す
export function calc(price, ...codes) {
let total = price;
for (const code of codes) {
if (code === '10%OFF') total = total * 0.9;
else if (code === '500OFF') total = total - 500;
}
return Math.max(0, Math.round(total));
}
仕様は「併用したときは、値引きの大きい方だけ」です。1000円に '10%OFF' と '500OFF' を併用すると、正しくは 500円。この関数は 400円を返します。順番を逆にすると 450円です。
この関数に、頼み方だけを変えてテストを書かせました。AI には、どこにバグがあるかを教えていません。TDD の手順を踏ませれば安心かは、識者の間でも割れています(後の節で紹介)。そこで、手順と期待値の出どころを変えて並べました。頼み方の全文は、結果のすぐ後の「手元で試す」に置いています。
頼み方
期待値の出どころ
①
仕様は渡さず「この関数のテストを書いて」
目の前の実装
②
仕様の文を渡し「先にテストを書いて、通るように直して」(TDD の頼み方)
人が書いた仕様の文
③
人が書いた受入表を渡し「表のままテストにして。関数はまだ直さないで」
人が書いた表
①に仕様を渡していないのは、バグ入りの実装だけを見せてテストを書かせる形にするためです。ISSTA 2026 の論文(すぐ後の①の節で紹介)が調べたのと同じ設定です。和田卓人さんも、実装の後にテストを書かせると、そのコードに都合のいいテストを作ることがある、と書いています。①はその条件にも当たります。仕様を渡したうえで後から書かせる形は、試していません。
②は、関数がもうある所へ後から TDD の頼み方をした形です。実装より先にテストを書く本来の形とは違います。
3通りの結果
答えを読む前に動画で見るなら、6:30 からです。③で Claude Code に受入表を渡す画面から、REQ-003 の赤が出るまでです → youtu.be
①だけがバグを見逃しました。①と③は、どちらも後から書かせています
Claude の3つのモデルに、頼み方ごとに5回ずつ頼みました。内訳です。
数えたもの
Haiku
Sonnet
Opus
計
① 併用の行の期待値がバグの値(400/450 など)で、全部緑
5/5
5/5
5/5
15/15
② 最初に書いた併用の期待値が仕様の値で、最初の実行が赤
5/5
5/5
5/5
15/15
② 赤のあと、テストの期待値を書き換えた
0/5
0/5
0/5
0/15
② 最後の calc が、受入表の5行とテストに無い3行を通す
5/5
5/5
5/5
15/15
③ 期待値を変えずにテスト化し、REQ-003・REQ-004 が赤
5/5
5/5
5/5
15/15
材料はこの関数1つです。モデルごとの差は出ませんでした。
① 実装から書かせた:バグの値で緑
15回とも、併用の行の期待値はバグの値でした。全部緑で終わり、関数は変わっていません。ある回(Sonnet)のテストの一部です。
tests/cart.test.js(①・抜粋)
it('複数コードは順番に適用される', () => {
expect(calc(1000, '10%OFF', '500OFF')).toBe(400);
expect(calc(1000, '500OFF', '10%OFF')).toBe(450);
});
①の実行結果
✓ tests/cart.test.js (7 tests) 2ms
Test Files 1 passed (1)
Tests 7 passed (7)
バグの振る舞いに「複数コードは順番に適用される」と、仕様のような名前が付いています。Haiku のある回は、最後の報告で「テストは現在の実装の動作を記述しています」と断っていました。そのうえで「適用順で結果が変わる」ことを仕様として固定した、と書いて緑で終えています。
ISSTA 2026 の論文(Zhao, Zhou, Cohen・arXiv 2607.22880)にも、同じ報告があります。LLM はバグ入りの実装に引きずられ、バグった振る舞いを確かめるテストを書くことがある、というものです。この場合、カバレッジは当てにならず、ミューテーション分析(わざとコードを壊して、テストが赤になるかを見る方法)も使えないそうです。ただし対象は 2025 年の11モデルです。今のモデルが必ずそうなる、とまでは言えません。
和田さんの言う「都合のいいテスト」とは、実装の後から書かせる点が重なります。ただ、①の関数は人が用意したもので、AI 自身が書いたコードではありません。
② 仕様の文から先に書かせた:赤を見て、関数を直した
15回とも、最初に書いた併用の期待値は仕様どおり(500 など)で、最初の実行は赤でした。ある回(Opus)の最初のテストを、元の関数に当てた出力です。
②の最初のテストを元の関数に当てた結果(抜粋)
× tests/cart.test.js > calc > 併用時、500OFF の方が値引きが大きければ500OFFのみ適用 3ms
→ expected 400 to be 500 // Object.is equality
× tests/cart.test.js > calc > 併用時、10%OFF の方が値引きが大きければ10%OFFのみ適用 0ms
→ expected 8500 to be 9000 // Object.is equality
そのあと15回とも、テストはそのままで、関数の方を直しました。同じ回の差分です。
src/cart.js(②の直し)
// 割引コードを適用した支払額(円)を返す
+// 複数コード指定時は、値引き額が最大のものだけを適用する
export function calc(price, ...codes) {
- let total = price;
- let discount = 0;
for (const code of codes) {
- if (code === '10%OFF') total = total * 0.9;
- else if (code === '500OFF') total = total - 500;
- let d = 0;
- if (code === '10%OFF') d = price * 0.1;
- else if (code === '500OFF') d = 500;
- discount = Math.max(discount, d);
}
- return Math.max(0, Math.round(total));
- return Math.max(0, Math.round(price - discount));
}
TDD を再発見して広めた Kent Beck は、テストを通すために定数を返す近道に気をつけろ、と言っています。出どころは Prodacity 2026 の講演(40:21〜)です。
You have to watch for returning constants.
(定数を返していないか、見張らなければならない)
そこで、直した関数に③の受入表の5行と、テストに無い次の3行を当てました。
入力
仕様どおりの値(円)
calc(2000, '10%OFF', '500OFF')
1500
calc(8000, '10%OFF', '500OFF')
7200
calc(400, '10%OFF', '500OFF')
0
15回とも8行すべて仕様どおりで、定数の近道はありませんでした。
ただ、②だけでは、効いたのが「先に書く手順」か「仕様の文」かは分けられません。①から2つを同時に変えているからです。
③ 人の受入表を渡した:REQ-003 が赤
15回とも、期待値を表のまま使い、関数には触れませんでした。ある回(Sonnet)の出力です。
③の実行結果(抜粋)
❯ tests/acceptance.test.js (5 tests | 2 failed) 6ms
✓ 受入テスト > REQ-001: calc(1000) は 1000 1ms
✓ 受入テスト > REQ-002: calc(1000, '10%OFF') は 900 0ms
× 受入テスト > REQ-003: calc(1000, '10%OFF', '500OFF') は 500 4ms
→ expected 400 to be 500 // Object.is equality
× 受入テスト > REQ-004: calc(1000, '500OFF', '10%OFF') は 500 0ms
→ expected 450 to be 500 // Object.is equality
✓ 受入テスト > REQ-005: calc(300, '500OFF') は 0 0ms
①で緑のまま通ったバグが、ここでは赤で見えました。
分けたのは、期待値の出どころ
①と③は、どちらも後から書かせただけです。違いは、期待値をどこから持ってきたか。①は目の前の実装から、③は実装と関係の無い表から取りました。②の期待値も、人が書いた仕様の文から来ています。
3通りを並べると、分かれ目は手順よりも、期待値を実装と共有しない場所に置けるかでした。Beck は同じ講演で、AI は近道をしたがり、テストケースの意図をくぐり抜けることがある、とも言っています。期待値の出どころは、AI に任せきりにできません。
手元で試す
材料
結果の節と同じ、バグを1か所入れた関数です。
src/cart.js(バグ入り)
src/cart.js
// 割引コードを適用した支払額(円)を返す
export function calc(price, ...codes) {
let total = price;
for (const code of codes) {
if (code === '10%OFF') total = total * 0.9;
else if (code === '500OFF') total = total - 500;
}
return Math.max(0, Math.round(total));
}
準備
材料の src/cart.js と、次の package.json を置きます。
package.json
{
"name": "cart",
"private": true,
"type": "module",
"devDependencies": { "vitest": "3.2.4" }
}
最初の状態を git にコミットします。空のディレクトリは git に入らないので、tests/ には空の .gitkeep を置きます。
mkdir -p cart/src cart/tests && cd cart
src/cart.js と package.json を上のとおりに置く
touch tests/.gitkeep
echo "node_modules/" > .gitignore
npm install
git init
git add -A && git commit -m "バグ入りの calc"
頼むときは1回ごとに、このディレクトリを丸ごと複製します(cp -r cart cart-run1 など)。前の回で AI が関数を直していると、次の比較になりません。
3つの頼み方(全文)
準備したディレクトリの複製の中で、そのまま貼って頼みます。
頼み方①
src/cart.js の calc 関数のテストを tests/cart.test.js に書いて、npx vitest run で全部通ることを確かめて。
頼み方②
src/cart.js の calc について、次の仕様のテストを先に tests/cart.test.js に書いて、npx vitest run で実行して。テストが通らなければ、テストが通るように calc を実装して。
仕様:
calc(price, ...codes) は、割引コードを適用した支払額(円)を返す。
使えるコードは '10%OFF'(価格の10%引き)と '500OFF'(500円引き)。
2つを併用したときは、値引き額の大きい方だけを適用する。
支払額は0円未満にならない。
頼み方③
次の受入表の各行を、テスト名に REQ-ID を入れたテストにして tests/acceptance.test.js に書き、npx vitest run で実行して結果を見せて。期待値は表の値をそのまま使い、変えないで。calc はまだ直さないで。
| REQ | 入力 | 期待値(円) |
|---|---|---|
| REQ-001 | calc(1000) | 1000 |
| REQ-002 | calc(1000, '10%OFF') | 900 |
| REQ-003 | calc(1000, '10%OFF', '500OFF') | 500 |
| REQ-004 | calc(1000, '500OFF', '10%OFF') | 500 |
| REQ-005 | calc(300, '500OFF') | 0 |
③の「calc はまだ直さないで」は、赤を見るためです。
試した環境と回数
項目
内容
試した環境
Claude Code 2.1.305 の非対話モード(CLAUDE.md・フック・個人設定なし)、Node.js v22.22.1、vitest 3.2.4、JavaScript(ES モジュール)
試したモデル
Claude の3つのモデル(Haiku 5.5・Sonnet 5.5・Opus 5.5)
回数
3つの頼み方 × 3モデル × 各5回=45回。1回ごとに、コミットした最初の状態の複製から始めた
試した日
2026-10-20
一次資料の確認日
2026-10-19〜10-21
非対話モードで回数を打つときのコマンド
--bare は CLAUDE.md・フック・個人設定を読まない指定です。bypassPermissions は確認なしでファイルを書き換えるので、捨ててよい複製の中だけで使います。
claude -p --bare --model claude-sonnet-5-5 \
--tools Read Write Edit Bash Glob Grep \
--permission-mode bypassPermissions \
"src/cart.js の calc 関数のテストを tests/cart.test.js に書いて、npx vitest run で全部通ることを確かめて。"
モデル名を claude-haiku-5-5・claude-opus-5-5 に替え、頼み方を②③に替えて同じように打ちました。終わったら git diff src/cart.js で関数が変わったかを見て、npx vitest run をもう一度実行して結果を残しました。
自分のプロジェクトで:受入表と REQ-ID
期待値は人が決めて、表に書く
入力と期待値を表にします。草案は AI に出させてかまいません。期待値の数字だけは、仕様から人が決めます。②で AI が 500 を当てられたのも、元に人が書いた仕様の文があったからです。
Kent C. Dodds も、原則文書(kody・2026-10-06)にこう書いています。「アサーションには、本番のコードと共有しない判定基準が要る」。原文は "An assertion needs an oracle the production code does not share." です。受入表は、その判定基準の置き場です。新しい道具も、別料金も要りません。
テスト名に REQ-ID を入れる
③では「テスト名に REQ-ID を入れて」と頼みました。こうすると、テストの結果がそのまま要件ごとの合否表になります。
③の実行結果を、受入表の行と並べた図です。どの要件が落ちたか、ひと目で分かります
わざと壊す確認では見抜けない
①の節で触れたミューテーション分析を、手で簡単にやる確認があります。関数をわざと壊して、テストが赤になるかを見るものです。でも①のテストは、400 や 450 という決まった数字を確かめています。関数を壊して答えが変われば、赤になります。これは試した結果ではなく、テストの形から言えることです。バグの値で固めたテストは、この確認では見抜けません。見抜けるのは、実装と共有しない期待値と照らしたときです。
AI のテストを見たら、まず確かめること
確かめるのは1つ。期待値の数字を、実装の外の出どころへたどって照らします。
AI が書いたテストを1本開き、toBe(…) などに渡した期待値の数字を見る
受入表があれば、テスト名の REQ-ID から表の行へたどり、同じ値かを見る
表が無ければ、仕様の文・チケット・自分の計算と照らす
たどれる表が無いなら、REQ-ID 付きの受入表を3行だけ書き、テスト名に番号を入れる
緑でも、期待値がバグの値なら、何も確かめていません。
下敷きの記事:TDD を指示しても、はっきりした差は出なかった
ここまでの比較は、Birgitta Böckeler の記事を起点に組みました。載ったのは martinfowler.com(2026-08-10)です。題は「TDD inside the agent loop - theater or actual value?」。訳すと「エージェントのループの中の TDD は、劇場か、本当の価値か?」で、劇場は、やっている“ふり”のたとえです。
エージェントに TDD の手順を指示したときと、しないときで出来上がりを比べた。本人いわく「小さな非公式の評価」。解答は Sonnet 4.6、出来の比較は作り方を伏せて Opus 4.8。実行は 2026 年7月上旬
手順の有無で、はっきりした差は出なかった。ミューテーションスコア(壊したコードをテストが赤にできた割合)にも、意味のある差は無かった
トークンは、TDD ありの方が総量で最大約 8.5 倍。ただしキャッシュの読み込みも同じ重みで数えた総量で、実費は大きめに出ている可能性が高い、と本人が書いている
一部のセッションでは、テストが実装の出力をそのまま期待値にしていた。テストを先に書かせても、これは確実には防げなかった
結論は「エージェントにテストを先に書けと言うのは、個人的にやめた」。代わりに、成果を見張ってフィードバックする
一部のセッションの形を模式で書くと、こうなります。期待値を実装から取っている点で、①と同じ出どころです。
説明のための模式(記事に載ったコードではありません)
const expected = calc(1000, '10%OFF', '500OFF'); // 期待値を実装で計算している
expect(calc(1000, '10%OFF', '500OFF')).toBe(expected); // 実装が何を返しても緑になる
記事が挙げる「成果を見張る」手
ミューテーションテストで、回帰テストの質を見て直す
静的解析の結果をエージェントに渡す
構造とモジュール性を、定期的に見直す
1つの変更で触ったファイル数・トークン数の推移を見る
Approved Scenarios(人が確かめたシナリオの期待値を凍結し、破れたら人が承認し直す半手動のテスト)
識者の見方:エージェントに TDD の手順を踏ませるか
問いは1つ、「エージェントに TDD の手順を踏ませるか」です。3人は互いに言い合っているわけではなく、別々の場で違う答えをしています。
出どころは Böckeler・Willison・和田卓人(エンジニアtype)
Willison は「red/green TDD を使って」と頼むのが、短くてよく効く、と書いています。和田さんは、実装の後に書かせると都合のいいテストになることがあり、先に書かせるとハーネスとして効く、と書いています。
並べると、同じことを言っている所もあります(この記事の整理です)。Böckeler は「なぜ赤になったかを誰かが確かめて初めて、赤は証明になる」と書いています。和田さんも7月の資料で「AI エージェントは自己評価が苦手」と書いています。
割れているのは手順を指示するかどうかで、AI の自己評価を当てにしない点は揃っています。
原文の抜粋
Böckeler: "there was no clearly discernable difference based on TDD workflow versus no TDD workflow"(TDD の手順を踏んだかどうかで、はっきり見分けられる差は無かった)
Böckeler: "Writing the test first doesn't reliably prevent this."(テストを先に書いても、これは確実には防げない)
Böckeler: "I personally have stopped telling my coding agents to write tests first"(私自身、コーディングエージェントにテストを先に書けと言うのはやめた)
Böckeler: "Watching a test go red is only proof of anything if someone is checking why it went red."(なぜ赤になったかを誰かが確かめて初めて、赤は何かの証明になる)
Willison: ""Use red/green TDD" is a pleasingly succinct way to get better results out of a coding agent."(「red/green TDD を使って」は、エージェントから良い結果を引き出す、気持ちのいいほど短い言い方だ)
和田卓人(エンジニアtype):「AIに実装後のテストを書かせると、自分が書いたコードに都合のいいテストを作ることがあります。ですが「先にテストを書かせ、それをパスするようにコードを実装させる」というTDDの手順を踏ませると、ハーネスとしての効果が上がります。」
和田卓人(Speaker Deck・2026-07-23):「実行主体たる AI エージェントは自己評価が苦手で非決定論的な動作をする」
この比較で言えないこと
材料は関数1つの単体テストです。期待値の出どころの見方は結合テストや E2E でも同じですが、単体・結合・E2E のどこに何を任せるかは扱っていません
①の結果は、この関数で3モデル×5回の15回すべてです。どのコードでも必ずそうなる、とは言えません
仕様の文を渡したうえで、後からテストを書かせる形は試していません。①と③の違いには、仕様(期待値)を渡したかどうかも含まれます
Böckeler の比較は、人がテストシナリオを書く形を測っていません。③は、その比較の外の形です
まとめ
同じバグ入りの関数で、実装から書かせたテストは15回ともバグの値で緑でした。仕様の文や人の受入表から期待値を取ったテストは、15回ともバグを赤にしました
分かれ目は手順よりも、期待値を実装と共有しない場所に置けるかでした。TDD の手順を踏ませるかは、識者の間でも割れています
AI のテストを見たら、期待値の数字を受入表・仕様の文・チケット・自分の計算へたどって照らします。表が無ければ、REQ-ID 付きで3行書くところから始めます
このサンプルが引いた線は、期待値の数字は人が表に書き、テストへの書き起こしと実行は AI に任せる、というものでした。あなたなら、どこに線を引きますか
動画で見たい方はこちら(受入表を渡す画面から REQ-003 の赤まで・6:30〜)→ youtu.be
あわせて読みたい
AI コードレビュー、全部読む? 承認の人数をリスクで変える Anthropic の方式を CODEOWNERS とブランチ保護で組む: 「AI が開く PR を、人はどこまで読むか」を扱った記事で、この記事の「緑をどこまで信じるか」はその続きの問いです
次の主題は、単体・結合・E2E のどこに何を任せるか(テストの層)です。
再生リスト「AIコーディング考察」の2本目です: youtube.com
参考リンク
Birgitta Böckeler「TDD inside the agent loop - theater or actual value?」(martinfowler.com・2026-08-10): martinfowler.com
比較の実行ログ(Böckeler の記事の付属リポジトリ): birgitta410/tdd-comparisons
Simon Willison「Red/green TDD」(Agentic Engineering Patterns・2026-02-23): simonwillison.net
和田卓人さん(エンジニアtype・2026-10-05): type.jp
和田卓人さん(Speaker Deck・2026-07-23): speakerdeck.com
Zhao, Zhou, Cohen「Do Coverage and Mutation Scores of LLM-Generated Test Suites Correlate with Their Effectiveness? (Replicability Study)」(ISSTA 2026・arXiv 2607.22880): arxiv.org
Kent Beck「Software Engineering in the Age of AI」(Prodacity 2026・40:21〜): youtube.com
Kent C. Dodds(kody の原則文書・2026-10-06): github.com/kody-hq/kody/blob/main/docs/principles…
vitest CLI(vitest run): vitest.dev
Mentions
Kody v2026.11.01 is out. Execution contexts grow up. Windows is now a first-class platform for kody daemon and the desktop runner — native host folders, Task Scheduler background service, and clean process-tree shutdown. Every daemon… github.com/kody-hq/kody/releases/tag/v2026.11.01
AI が書いたテストの緑は信じていい? 15回ともバグの値で緑だった:Claude Code と TDD で3通りの頼み方を比べる Claude Code にテストを書かせると全部緑で返ってきて、そのまま信じていいか迷うことがあります。 同じバグ入りの関数で試すと、実装から書かせたテストは15回ともバグの値で緑、人の受入表から書かせたテストは15回ともそのバグを赤にしました。 今日やるのは1つ、AI のテストの期待値の数字を、人が書いた仕様や受入表と照らすことです。 この下には、3通りの頼み方の全文、3モデル×5回の内訳、手元で試す手順、出典があります。 ! この記事の内容は動画でも解説しています。③で Claude Code の入力欄に受入表を渡す画面から、REQ-003 の赤が出るまで(6:30〜)を、約10分の動画で追えます。 動画は 10/27 07:00 公開予定です → youtu.be 結果:実装から書かせたテストだけが、15回ともバグの値で緑 試したこと:同じバグに、3通りの頼み方 期待値は、テストが「正しい答え」とする値です。expect(calc(1000)).toBe(1000) なら、toBe に渡した 1000 が期待値です。期待値そのものが間違っていたら、テストが緑でも何も確かめていません。 材料は、割引コードを使った支払額を返す関数です。バグをわざと1か所だけ入れました。2つのコードを併用すると、両方を順に引いてしまいます。 src/cart.js // 割引コードを適用した支払額(円)を返す export function calc(price, ...codes) { let total = price; for (const code of codes) { if (code === '10%OFF') total = total * 0.9; else if (code === '500OFF') total = total - 500; } return Math.max(0, Math.round(total)); } 仕様は「併用したときは、値引きの大きい方だけ」です。1000円に '10%OFF' と '500OFF' を併用すると、正しくは 500円。この関数は 400円を返します。順番を逆にすると 450円です。 この関数に、頼み方だけを変えてテストを書かせました。AI には、どこにバグがあるかを教えていません。TDD の手順を踏ませれば安心かは、識者の間でも割れています(後の節で紹介)。そこで、手順と期待値の出どころを変えて並べました。頼み方の全文は、結果のすぐ後の「手元で試す」に置いています。 頼み方 期待値の出どころ ① 仕様は渡さず「この関数のテストを書いて」 目の前の実装 ② 仕様の文を渡し「先にテストを書いて、通るように直して」(TDD の頼み方) 人が書いた仕様の文 ③ 人が書いた受入表を渡し「表のままテストにして。関数はまだ直さないで」 人が書いた表 ①に仕様を渡していないのは、バグ入りの実装だけを見せてテストを書かせる形にするためです。ISSTA 2026 の論文(すぐ後の①の節で紹介)が調べたのと同じ設定です。和田卓人さんも、実装の後にテストを書かせると、そのコードに都合のいいテストを作ることがある、と書いています。①はその条件にも当たります。仕様を渡したうえで後から書かせる形は、試していません。 ②は、関数がもうある所へ後から TDD の頼み方をした形です。実装より先にテストを書く本来の形とは違います。 3通りの結果 答えを読む前に動画で見るなら、6:30 からです。③で Claude Code に受入表を渡す画面から、REQ-003 の赤が出るまでです → youtu.be ①だけがバグを見逃しました。①と③は、どちらも後から書かせています Claude の3つのモデルに、頼み方ごとに5回ずつ頼みました。内訳です。 数えたもの Haiku Sonnet Opus 計 ① 併用の行の期待値がバグの値(400/450 など)で、全部緑 5/5 5/5 5/5 15/15 ② 最初に書いた併用の期待値が仕様の値で、最初の実行が赤 5/5 5/5 5/5 15/15 ② 赤のあと、テストの期待値を書き換えた 0/5 0/5 0/5 0/15 ② 最後の calc が、受入表の5行とテストに無い3行を通す 5/5 5/5 5/5 15/15 ③ 期待値を変えずにテスト化し、REQ-003・REQ-004 が赤 5/5 5/5 5/5 15/15 材料はこの関数1つです。モデルごとの差は出ませんでした。 ① 実装から書かせた:バグの値で緑 15回とも、併用の行の期待値はバグの値でした。全部緑で終わり、関数は変わっていません。ある回(Sonnet)のテストの一部です。 tests/cart.test.js(①・抜粋) it('複数コードは順番に適用される', () => { expect(calc(1000, '10%OFF', '500OFF')).toBe(400); expect(calc(1000, '500OFF', '10%OFF')).toBe(450); }); ①の実行結果 ✓ tests/cart.test.js (7 tests) 2ms Test Files 1 passed (1) Tests 7 passed (7) バグの振る舞いに「複数コードは順番に適用される」と、仕様のような名前が付いています。Haiku のある回は、最後の報告で「テストは現在の実装の動作を記述しています」と断っていました。そのうえで「適用順で結果が変わる」ことを仕様として固定した、と書いて緑で終えています。 ISSTA 2026 の論文(Zhao, Zhou, Cohen・arXiv 2607.22880)にも、同じ報告があります。LLM はバグ入りの実装に引きずられ、バグった振る舞いを確かめるテストを書くことがある、というものです。この場合、カバレッジは当てにならず、ミューテーション分析(わざとコードを壊して、テストが赤になるかを見る方法)も使えないそうです。ただし対象は 2025 年の11モデルです。今のモデルが必ずそうなる、とまでは言えません。 和田さんの言う「都合のいいテスト」とは、実装の後から書かせる点が重なります。ただ、①の関数は人が用意したもので、AI 自身が書いたコードではありません。 ② 仕様の文から先に書かせた:赤を見て、関数を直した 15回とも、最初に書いた併用の期待値は仕様どおり(500 など)で、最初の実行は赤でした。ある回(Opus)の最初のテストを、元の関数に当てた出力です。 ②の最初のテストを元の関数に当てた結果(抜粋) × tests/cart.test.js > calc > 併用時、500OFF の方が値引きが大きければ500OFFのみ適用 3ms → expected 400 to be 500 // Object.is equality × tests/cart.test.js > calc > 併用時、10%OFF の方が値引きが大きければ10%OFFのみ適用 0ms → expected 8500 to be 9000 // Object.is equality そのあと15回とも、テストはそのままで、関数の方を直しました。同じ回の差分です。 src/cart.js(②の直し) // 割引コードを適用した支払額(円)を返す +// 複数コード指定時は、値引き額が最大のものだけを適用する export function calc(price, ...codes) { - let total = price; - let discount = 0; for (const code of codes) { - if (code === '10%OFF') total = total * 0.9; - else if (code === '500OFF') total = total - 500; - let d = 0; - if (code === '10%OFF') d = price * 0.1; - else if (code === '500OFF') d = 500; - discount = Math.max(discount, d); } - return Math.max(0, Math.round(total)); - return Math.max(0, Math.round(price - discount)); } TDD を再発見して広めた Kent Beck は、テストを通すために定数を返す近道に気をつけろ、と言っています。出どころは Prodacity 2026 の講演(40:21〜)です。 You have to watch for returning constants. (定数を返していないか、見張らなければならない) そこで、直した関数に③の受入表の5行と、テストに無い次の3行を当てました。 入力 仕様どおりの値(円) calc(2000, '10%OFF', '500OFF') 1500 calc(8000, '10%OFF', '500OFF') 7200 calc(400, '10%OFF', '500OFF') 0 15回とも8行すべて仕様どおりで、定数の近道はありませんでした。 ただ、②だけでは、効いたのが「先に書く手順」か「仕様の文」かは分けられません。①から2つを同時に変えているからです。 ③ 人の受入表を渡した:REQ-003 が赤 15回とも、期待値を表のまま使い、関数には触れませんでした。ある回(Sonnet)の出力です。 ③の実行結果(抜粋) ❯ tests/acceptance.test.js (5 tests | 2 failed) 6ms ✓ 受入テスト > REQ-001: calc(1000) は 1000 1ms ✓ 受入テスト > REQ-002: calc(1000, '10%OFF') は 900 0ms × 受入テスト > REQ-003: calc(1000, '10%OFF', '500OFF') は 500 4ms → expected 400 to be 500 // Object.is equality × 受入テスト > REQ-004: calc(1000, '500OFF', '10%OFF') は 500 0ms → expected 450 to be 500 // Object.is equality ✓ 受入テスト > REQ-005: calc(300, '500OFF') は 0 0ms ①で緑のまま通ったバグが、ここでは赤で見えました。 分けたのは、期待値の出どころ ①と③は、どちらも後から書かせただけです。違いは、期待値をどこから持ってきたか。①は目の前の実装から、③は実装と関係の無い表から取りました。②の期待値も、人が書いた仕様の文から来ています。 3通りを並べると、分かれ目は手順よりも、期待値を実装と共有しない場所に置けるかでした。Beck は同じ講演で、AI は近道をしたがり、テストケースの意図をくぐり抜けることがある、とも言っています。期待値の出どころは、AI に任せきりにできません。 手元で試す 材料 結果の節と同じ、バグを1か所入れた関数です。 src/cart.js(バグ入り) src/cart.js // 割引コードを適用した支払額(円)を返す export function calc(price, ...codes) { let total = price; for (const code of codes) { if (code === '10%OFF') total = total * 0.9; else if (code === '500OFF') total = total - 500; } return Math.max(0, Math.round(total)); } 準備 材料の src/cart.js と、次の package.json を置きます。 package.json { "name": "cart", "private": true, "type": "module", "devDependencies": { "vitest": "3.2.4" } } 最初の状態を git にコミットします。空のディレクトリは git に入らないので、tests/ には空の .gitkeep を置きます。 mkdir -p cart/src cart/tests && cd cart src/cart.js と package.json を上のとおりに置く touch tests/.gitkeep echo "node_modules/" > .gitignore npm install git init git add -A && git commit -m "バグ入りの calc" 頼むときは1回ごとに、このディレクトリを丸ごと複製します(cp -r cart cart-run1 など)。前の回で AI が関数を直していると、次の比較になりません。 3つの頼み方(全文) 準備したディレクトリの複製の中で、そのまま貼って頼みます。 頼み方① src/cart.js の calc 関数のテストを tests/cart.test.js に書いて、npx vitest run で全部通ることを確かめて。 頼み方② src/cart.js の calc について、次の仕様のテストを先に tests/cart.test.js に書いて、npx vitest run で実行して。テストが通らなければ、テストが通るように calc を実装して。 仕様: calc(price, ...codes) は、割引コードを適用した支払額(円)を返す。 使えるコードは '10%OFF'(価格の10%引き)と '500OFF'(500円引き)。 2つを併用したときは、値引き額の大きい方だけを適用する。 支払額は0円未満にならない。 頼み方③ 次の受入表の各行を、テスト名に REQ-ID を入れたテストにして tests/acceptance.test.js に書き、npx vitest run で実行して結果を見せて。期待値は表の値をそのまま使い、変えないで。calc はまだ直さないで。 | REQ | 入力 | 期待値(円) | |---|---|---| | REQ-001 | calc(1000) | 1000 | | REQ-002 | calc(1000, '10%OFF') | 900 | | REQ-003 | calc(1000, '10%OFF', '500OFF') | 500 | | REQ-004 | calc(1000, '500OFF', '10%OFF') | 500 | | REQ-005 | calc(300, '500OFF') | 0 | ③の「calc はまだ直さないで」は、赤を見るためです。 試した環境と回数 項目 内容 試した環境 Claude Code 2.1.305 の非対話モード(CLAUDE.md・フック・個人設定なし)、Node.js v22.22.1、vitest 3.2.4、JavaScript(ES モジュール) 試したモデル Claude の3つのモデル(Haiku 5.5・Sonnet 5.5・Opus 5.5) 回数 3つの頼み方 × 3モデル × 各5回=45回。1回ごとに、コミットした最初の状態の複製から始めた 試した日 2026-10-20 一次資料の確認日 2026-10-19〜10-21 非対話モードで回数を打つときのコマンド --bare は CLAUDE.md・フック・個人設定を読まない指定です。bypassPermissions は確認なしでファイルを書き換えるので、捨ててよい複製の中だけで使います。 claude -p --bare --model claude-sonnet-5-5 \ --tools Read Write Edit Bash Glob Grep \ --permission-mode bypassPermissions \ "src/cart.js の calc 関数のテストを tests/cart.test.js に書いて、npx vitest run で全部通ることを確かめて。" モデル名を claude-haiku-5-5・claude-opus-5-5 に替え、頼み方を②③に替えて同じように打ちました。終わったら git diff src/cart.js で関数が変わったかを見て、npx vitest run をもう一度実行して結果を残しました。 自分のプロジェクトで:受入表と REQ-ID 期待値は人が決めて、表に書く 入力と期待値を表にします。草案は AI に出させてかまいません。期待値の数字だけは、仕様から人が決めます。②で AI が 500 を当てられたのも、元に人が書いた仕様の文があったからです。 Kent C. Dodds も、原則文書(kody・2026-10-06)にこう書いています。「アサーションには、本番のコードと共有しない判定基準が要る」。原文は "An assertion needs an oracle the production code does not share." です。受入表は、その判定基準の置き場です。新しい道具も、別料金も要りません。 テスト名に REQ-ID を入れる ③では「テスト名に REQ-ID を入れて」と頼みました。こうすると、テストの結果がそのまま要件ごとの合否表になります。 ③の実行結果を、受入表の行と並べた図です。どの要件が落ちたか、ひと目で分かります わざと壊す確認では見抜けない ①の節で触れたミューテーション分析を、手で簡単にやる確認があります。関数をわざと壊して、テストが赤になるかを見るものです。でも①のテストは、400 や 450 という決まった数字を確かめています。関数を壊して答えが変われば、赤になります。これは試した結果ではなく、テストの形から言えることです。バグの値で固めたテストは、この確認では見抜けません。見抜けるのは、実装と共有しない期待値と照らしたときです。 AI のテストを見たら、まず確かめること 確かめるのは1つ。期待値の数字を、実装の外の出どころへたどって照らします。 AI が書いたテストを1本開き、toBe(…) などに渡した期待値の数字を見る 受入表があれば、テスト名の REQ-ID から表の行へたどり、同じ値かを見る 表が無ければ、仕様の文・チケット・自分の計算と照らす たどれる表が無いなら、REQ-ID 付きの受入表を3行だけ書き、テスト名に番号を入れる 緑でも、期待値がバグの値なら、何も確かめていません。 下敷きの記事:TDD を指示しても、はっきりした差は出なかった ここまでの比較は、Birgitta Böckeler の記事を起点に組みました。載ったのは martinfowler.com(2026-08-10)です。題は「TDD inside the agent loop - theater or actual value?」。訳すと「エージェントのループの中の TDD は、劇場か、本当の価値か?」で、劇場は、やっている“ふり”のたとえです。 エージェントに TDD の手順を指示したときと、しないときで出来上がりを比べた。本人いわく「小さな非公式の評価」。解答は Sonnet 4.6、出来の比較は作り方を伏せて Opus 4.8。実行は 2026 年7月上旬 手順の有無で、はっきりした差は出なかった。ミューテーションスコア(壊したコードをテストが赤にできた割合)にも、意味のある差は無かった トークンは、TDD ありの方が総量で最大約 8.5 倍。ただしキャッシュの読み込みも同じ重みで数えた総量で、実費は大きめに出ている可能性が高い、と本人が書いている 一部のセッションでは、テストが実装の出力をそのまま期待値にしていた。テストを先に書かせても、これは確実には防げなかった 結論は「エージェントにテストを先に書けと言うのは、個人的にやめた」。代わりに、成果を見張ってフィードバックする 一部のセッションの形を模式で書くと、こうなります。期待値を実装から取っている点で、①と同じ出どころです。 説明のための模式(記事に載ったコードではありません) const expected = calc(1000, '10%OFF', '500OFF'); // 期待値を実装で計算している expect(calc(1000, '10%OFF', '500OFF')).toBe(expected); // 実装が何を返しても緑になる 記事が挙げる「成果を見張る」手 ミューテーションテストで、回帰テストの質を見て直す 静的解析の結果をエージェントに渡す 構造とモジュール性を、定期的に見直す 1つの変更で触ったファイル数・トークン数の推移を見る Approved Scenarios(人が確かめたシナリオの期待値を凍結し、破れたら人が承認し直す半手動のテスト) 識者の見方:エージェントに TDD の手順を踏ませるか 問いは1つ、「エージェントに TDD の手順を踏ませるか」です。3人は互いに言い合っているわけではなく、別々の場で違う答えをしています。 出どころは Böckeler・Willison・和田卓人(エンジニアtype) Willison は「red/green TDD を使って」と頼むのが、短くてよく効く、と書いています。和田さんは、実装の後に書かせると都合のいいテストになることがあり、先に書かせるとハーネスとして効く、と書いています。 並べると、同じことを言っている所もあります(この記事の整理です)。Böckeler は「なぜ赤になったかを誰かが確かめて初めて、赤は証明になる」と書いています。和田さんも7月の資料で「AI エージェントは自己評価が苦手」と書いています。 割れているのは手順を指示するかどうかで、AI の自己評価を当てにしない点は揃っています。 原文の抜粋 Böckeler: "there was no clearly discernable difference based on TDD workflow versus no TDD workflow"(TDD の手順を踏んだかどうかで、はっきり見分けられる差は無かった) Böckeler: "Writing the test first doesn't reliably prevent this."(テストを先に書いても、これは確実には防げない) Böckeler: "I personally have stopped telling my coding agents to write tests first"(私自身、コーディングエージェントにテストを先に書けと言うのはやめた) Böckeler: "Watching a test go red is only proof of anything if someone is checking why it went red."(なぜ赤になったかを誰かが確かめて初めて、赤は何かの証明になる) Willison: ""Use red/green TDD" is a pleasingly succinct way to get better results out of a coding agent."(「red/green TDD を使って」は、エージェントから良い結果を引き出す、気持ちのいいほど短い言い方だ) 和田卓人(エンジニアtype):「AIに実装後のテストを書かせると、自分が書いたコードに都合のいいテストを作ることがあります。ですが「先にテストを書かせ、それをパスするようにコードを実装させる」というTDDの手順を踏ませると、ハーネスとしての効果が上がります。」 和田卓人(Speaker Deck・2026-07-23):「実行主体たる AI エージェントは自己評価が苦手で非決定論的な動作をする」 この比較で言えないこと 材料は関数1つの単体テストです。期待値の出どころの見方は結合テストや E2E でも同じですが、単体・結合・E2E のどこに何を任せるかは扱っていません ①の結果は、この関数で3モデル×5回の15回すべてです。どのコードでも必ずそうなる、とは言えません 仕様の文を渡したうえで、後からテストを書かせる形は試していません。①と③の違いには、仕様(期待値)を渡したかどうかも含まれます Böckeler の比較は、人がテストシナリオを書く形を測っていません。③は、その比較の外の形です まとめ 同じバグ入りの関数で、実装から書かせたテストは15回ともバグの値で緑でした。仕様の文や人の受入表から期待値を取ったテストは、15回ともバグを赤にしました 分かれ目は手順よりも、期待値を実装と共有しない場所に置けるかでした。TDD の手順を踏ませるかは、識者の間でも割れています AI のテストを見たら、期待値の数字を受入表・仕様の文・チケット・自分の計算へたどって照らします。表が無ければ、REQ-ID 付きで3行書くところから始めます このサンプルが引いた線は、期待値の数字は人が表に書き、テストへの書き起こしと実行は AI に任せる、というものでした。あなたなら、どこに線を引きますか 動画で見たい方はこちら(受入表を渡す画面から REQ-003 の赤まで・6:30〜)→ youtu.be あわせて読みたい AI コードレビュー、全部読む? 承認の人数をリスクで変える Anthropic の方式を CODEOWNERS とブランチ保護で組む: 「AI が開く PR を、人はどこまで読むか」を扱った記事で、この記事の「緑をどこまで信じるか」はその続きの問いです 次の主題は、単体・結合・E2E のどこに何を任せるか(テストの層)です。 再生リスト「AIコーディング考察」の2本目です: youtube.com 参考リンク Birgitta Böckeler「TDD inside the agent loop - theater or actual value?」(martinfowler.com・2026-08-10): martinfowler.com 比較の実行ログ(Böckeler の記事の付属リポジトリ): birgitta410/tdd-comparisons Simon Willison「Red/green TDD」(Agentic Engineering Patterns・2026-02-23): simonwillison.net 和田卓人さん(エンジニアtype・2026-10-05): type.jp 和田卓人さん(Speaker Deck・2026-07-23): speakerdeck.com Zhao, Zhou, Cohen「Do Coverage and Mutation Scores of LLM-Generated Test Suites Correlate with Their Effectiveness? (Replicability Study)」(ISSTA 2026・arXiv 2607.22880): arxiv.org Kent Beck「Software Engineering in the Age of AI」(Prodacity 2026・40:21〜): youtube.com Kent C. Dodds(kody の原則文書・2026-10-06): github.com/kody-hq/kody/blob/main/docs/principles… vitest CLI(vitest run): vitest.dev
Kody v2026.10.12 is out. Stable credential ids and grants keyed by id. Every org credential — secret entry, OAuth integration, MCP server connection, vault binding — now carries a stable id. Use, Write, and Delete grants are keyed by that id under… github.com/kentcdodds/kody/releases/tag/v2026.10.…
Kody v2026.10.11 is out. Every trigger runs as its enabler. Cron jobs, webhooks, subscriptions, and inbound email now execute with the permissions of the person who enabled them — checked live on every run, never falling back to org Owner… github.com/kentcdodds/kody/releases/tag/v2026.10.…
Kody v2026.10.10 is out. Organization management is now a first-class web experience. Head to /@slug/-/settings to rename your org, upload an avatar, or soft-delete a team org. The members page at /@slug/-/members lets you invite people, change… github.com/kentcdodds/kody/releases/tag/v2026.10.…
Kody v2026.10.05 is out. Focused /connect/secret-set page for agent secret links. When an agent sends someone a link to set a secret, they now land on a clean one-action page instead of the full secrets list. Prefilled /account/secrets/new?name=… links… github.com/kentcdodds/kody/releases/tag/v2026.10.…