「テスト書いて」と「テスト駆動で実装して」は全く別物 — AI×TDD で品質が劇的に変わる構造的理由

@neurostack_0001 氏のポストが、AI にテストを書かせる際の決定的な違いを指摘し、大きな反響を呼んでいます(いいね 267、ブックマーク 222)。

3ヶ月AIにテストコード書かせてわかったこと。

「テスト書いて」と「テスト駆動で実装して」は全く別物だった。

3ヶ月間の実体験から導き出された結論は明快です。AI に「テストを書いて」と頼むのと「テスト駆動で実装して」と頼むのでは、出力されるテストの品質が根本的に異なる。本記事では、なぜこの違いが生まれるのか、その構造的な理由と実践的なワークフローを解説します。

「テスト書いて」が失敗する構造

テスト後付けバイアス

ポスト主が最初に経験した失敗パターンは、多くの開発者に共通するものです。

最初はClaude Codeに「この関数のテスト書いて」と頼んでた。構文は完璧。でも実行すると半分以上落ちる。テスト対象もモックしてたり、存在しないメソッド呼んでたり。「テストっぽいもの」を量産してただけ。

この問題はテスト後付けバイアスと呼ばれる LLM の構造的な弱点に起因します。LLM が実装コードを見てからテストを生成する場合、テストは「コードが何をすべきか」ではなく「コードが何をしているか」を検証するものになりがちです。

具体的に発生する問題は以下の通りです。

問題説明
テスト対象のモック化テストすべき関数自体をモックしてしまい、実際のロジックを検証していない
存在しないメソッド呼び出しLLM のハルシネーションにより、実在しない API やメソッドをテストで使用する
実装への密結合内部実装の詳細に依存するテストが生成され、リファクタリングで壊れる
網羅性の欠如エッジケースや異常系のテストが不足し、正常系のみカバーする

なぜ LLM は「テストっぽいもの」を量産するのか

Codemanship の記事が、この問題の本質を指摘しています。

The more things we ask models to pay attention to, the less able they are to pay attention to any of them.

LLM は「次の最も確率の高いトークン」を予測する仕組みです。既存の実装コードをコンテキストに含めてテストを生成すると、モデルは実装の構造を模倣したテストを生成します。テストとしての妥当性ではなく、「テストとして見た目がそれらしいもの」を出力するのです。

これは LLM の根本的な限界であり、プロンプトの工夫だけでは解決できません。

「テスト駆動で実装して」が品質を変える理由

テストファーストが生む構造的な違い

ポスト主が発見した転機は、TDD のループを AI 自身にやらせることでした。

  1. 要件だけ伝える
  2. テストを先に書かせる
  3. 実行→失敗確認もAIにやらせる
  4. テスト通るまでAIに反復させる

このアプローチが効果的な理由は、テスト生成時に実装コードがコンテキストに存在しないことにあります。

観点「テスト書いて」「テスト駆動で実装して」
テスト生成時のコンテキスト実装コード + 要件要件のみ
テストの検証対象「コードが何をしているか」「コードが何をすべきか」
ハルシネーションテスト実行まで検出されないRed フェーズで即座に検出される
フィードバックループなし生成→実行→修正の自動ループ

Red-Green-Refactor ループの AI 実装

従来の TDD では、開発者が手動で Red(テスト失敗)→ Green(テスト成功)→ Refactor(リファクタリング)のサイクルを回します。AI エージェントにこのループを回させると、以下のように動作します。

[AI × TDD の Red-Green-Refactor ループ]

RED フェーズ(テスト作成)
  ├── 要件からテストを生成(実装コードなし)
  ├── テストを実行 → 失敗を確認
  └── 失敗しないテスト → テストを修正(テスト自体の品質検証)

GREEN フェーズ(最小実装)
  ├── テストを通す最小限のコードを生成
  ├── テストを実行 → 成功を確認
  └── 失敗 → コードを修正して再実行

REFACTOR フェーズ(改善)
  ├── コードの重複・可読性を改善
  ├── テストを実行 → 全テスト成功を確認
  └── 失敗 → リファクタリングを修正

alexop.dev の実践記事では、Claude Code でこのループを強制する方法が紹介されています。Skills でワークフローを定義し、各フェーズ間に「テスト失敗を確認するまで次に進まない」というゲートを設けることで、AI が TDD のサイクルを構造的に遵守するようにしています。

「自己検証ループ」がハルシネーションを淘汰する

ポスト主が見出した核心的な洞察はここにあります。

コツは「AIに自分の出力を検証させる」こと。生成→実行→修正のループをAI内で完結させると、ハルシネーションが自然に淘汰される。

これは**調整ループ(Reconciliation Loop)**と呼ばれる概念です。テストから始める Agentic Coding のスライドでは、開発プロセスを「記述された理想状態と現在の状態を比較し、差分がなくなるまで調整する」ループとして再定義しています。

テストは AI にとって客観的な評価関数として機能します。LLM が生成したコードが「正しいかどうか」をテストの成否で判定できるため、ハルシネーションは以下のプロセスで自然に淘汰されます。

  1. 生成: AI がコードを生成する
  2. 実行: テストスイートを実行する
  3. 検証: テスト結果がフィードバックとして返る
  4. 修正: 失敗箇所を AI が分析し、修正する
  5. 反復: 全テスト成功まで 2〜4 を繰り返す

存在しないメソッドを呼ぶコードは、テスト実行時にエラーになります。テスト対象をモックしてしまうテストは、Red フェーズで「失敗すべきなのに成功する」ことで検出されます。ハルシネーションがコンパイラとテストランナーによって物理的に排除されるのです。

TDD の種類と AI の使い分け

「テストを一括で書かせる」は TDD ではない

Zenn の記事が重要な区別を指摘しています。TDD の生みの親 Kent Beck は、テストを一括で事前に書くアプローチを「よくある間違い」と指摘しています。

アプローチ方法適用場面
TDD(Red-Green-Refactor)1テストずつ書いて実装を育てる複雑度が高い、設計を育てたい
テスト事前一括作成全テストを先に書いてから実装要件が明確、不確実性が低い
ハイブリッド受入テストは一括、実装はTDD実務的なバランス

AI 時代では、テスト事前一括作成のデメリット(手戻りコスト)が軽減されます。AI が実装を高速にサポートするため、設計変更による手戻りが小さくなるからです。ただし、複雑なロジックについては依然として 1 テストずつの TDD サイクルが有効です。

CLAUDE.md で TDD を強制する

Claude Code で TDD を実践するための CLAUDE.md 設定例を紹介します。

1
2
3
4
5
6
7
8
## テスト駆動開発ルール

- 全ての実装はテストファーストで行うこと
- テストを書いたら必ず実行して失敗を確認すること(Red)
- テストが通る最小限のコードを書くこと(Green)
- テストが全て通った後にリファクタリングすること(Refactor)
- テスト対象の関数・クラス自体をモックしないこと
- テストを実行せずに実装に進まないこと

より高度な設定として、Skills と Hooks を組み合わせたワークフローでは、以下の構造で TDD を強制できます。

  • Skills: Red → Green → Refactor のフェーズを定義し、各フェーズ間にゲートを設ける
  • Hooks: UserPromptSubmit フックでプロンプトを評価し、TDD スキルを自動的にトリガーする
  • サブエージェント: テスト作成者・実装者・リファクタリング担当をサブエージェントとして分離し、コンテキスト汚染を防ぐ

サブエージェントの分離は特に重要です。テスト作成エージェントと実装エージェントを分けることで、「LLM が無意識に実装を想定してテストを書く」問題を構造的に防止できます。

t_wada 氏が語る「テストはガードレール」

Agile Journey の対談記事で、日本の TDD 第一人者である t_wada 氏(和田卓人氏)が、AI エージェント時代のテストの役割を「ガードレール」と表現しています。

Kent Beck も同様の見解を示しています。

「役割は良いコードとは何かをAIに教える人へと変わっています。テストがそのカリキュラムです」

AI が書くコードの品質を保証するのは、もはや人間のコードレビューだけではありません。テストスイートが自動的に品質を強制するのです。

この考え方を推し進めると、開発者の役割は以下のように変化します。

従来の役割AI 時代の役割
コードを書くテスト(仕様)を書く
テストで品質を検証するテストで AI の出力を制約する
コードレビューで品質を保つテスト + AI の自己検証ループで品質を保つ

実践的なステップ

ステップ 1: 「テスト書いて」から「テスト駆動で」に言い換える

最もシンプルな第一歩は、プロンプトの変更です。

❌ 「この関数のテスト書いて」
✅ 「この要件をテスト駆動で実装して。テストを先に書いて、実行して失敗を確認してから実装して」

ステップ 2: 実行を必須にする

AI がテストを生成しただけで満足しないよう、実行を明示的に求めます。

「テストを書いたら必ず実行して、失敗することを確認してから次に進んで」

ステップ 3: CLAUDE.md にルールを定義する

チーム全体で TDD を徹底するために、CLAUDE.md にルールを明記します。

ステップ 4: 人間のレビューポイントを決める

AI × TDD でも、人間のレビューは依然として重要です。

レビューポイント確認内容
テスト設計要件を正しく反映しているか、エッジケースは網羅されているか
テストの独立性テスト対象をモックしていないか、テスト間に依存がないか
仕様の妥当性ビジネスロジックとして正しいか(AI は「何を作るか」を判断できない)

まとめ

  • 「テスト書いて」と「テスト駆動で実装して」は構造的に異なる: 前者はテスト後付けバイアスにより「テストっぽいもの」を量産し、後者は要件からテストを生成するため品質が劇的に向上する
  • テスト後付けバイアスは LLM の構造的弱点: 実装コードをコンテキストに含めると、テストは「コードが何をすべきか」ではなく「コードが何をしているか」を検証するものになる
  • 自己検証ループがハルシネーションを淘汰する: 生成→実行→修正のループを AI 内で完結させると、存在しないメソッドやモック化されたテスト対象が物理的に排除される
  • Red-Green-Refactor を AI に回させる: テスト作成→失敗確認→最小実装→成功確認→リファクタリングのサイクルを AI 自身に実行させることが鍵
  • テストは AI へのカリキュラム: 開発者の役割は「コードを書く人」から「テスト(仕様)を書いて AI の出力を制約する人」に変化する
  • 人間のレビューは依然として必要: エッジケースの網羅性やビジネスロジックの妥当性は、AI だけでは判断できない

参考