Claude

Claude Code テスト駆動開発 完全ガイド【2026年最新】|AI×TDDで品質と速度を両立する実装戦略

2026年2月25日 105分で読める AQUA合同会社
Claude Code テスト駆動開発 完全ガイド【2026年最新】|AI×TDDで品質と速度を両立する実装戦略

AIコーディングツールの進化により、開発者はかつてない速度でコードを生成できるようになった。Claude Code、GitHub Copilot、Cursorといったツールは、自然言語の指示だけで数百行のコードを瞬時に生成する。しかし、この「速度」には代償がある。AIが生成するコードの品質を担保する仕組みがなければ、技術的負債は加速度的に蓄積され、プロダクションで致命的な障害を引き起こす。テストなきAIコーディングは、ブレーキのないスポーツカーに等しい。

この問題に対する解答が、テスト駆動開発(TDD)とClaude Codeの体系的な統合である。TDDは1999年にKent Beckが提唱して以来、ソフトウェア品質を担保するための最も厳格な開発手法として知られてきた。そしてClaude Codeには、CLAUDE.mdによるルール定義、Skillsによるワークフロー自動化、Hooksによる実行時ガードレール、そしてサブエージェントによるマルチエージェントアーキテクチャという4つの仕組みが備わっている。これらを組み合わせることで、AIに「テストを先に書き、最小実装で通し、リファクタリングする」というTDDの鉄則を構造的に強制できる。

本記事では、Claude CodeでTDDを実践するための包括的なガイドを提供する。CLAUDE.mdのテンプレート設計から、Skillsによるワークフロー自動発動、Hooksによるテスト実行の自動化、そしてマルチエージェントアーキテクチャによるコンテキスト分離まで、実際のコード例とともに解説する。さらに、mutation testingによる品質検証、CI/CDパイプラインとの統合、大規模プロジェクトでのスケーリング戦略まで踏み込む。

対象読者は、Claude Codeを日常的に使用している中級から上級の開発者である。TDDの基本概念は理解しているが、AIコーディングツールとの統合方法を模索している方、あるいはチーム全体でAI×TDDのワークフローを標準化したい方に向けて書かれている。TypeScriptとVitestを主な例として使用するが、考え方自体はどの言語・フレームワークにも適用可能だ。

この記事の内容



なぜAI時代にTDDが不可欠なのか——LLMの構造的弱点とテスト駆動の解

大規模言語モデル(LLM)は、膨大なコードコーパスから学習した統計的パターンに基づいてコードを生成する。この生成プロセスには、人間の開発者とは異なる構造的な弱点が内在している。テスト駆動開発は、これらの弱点を体系的に補完する唯一の方法論であると言える。本章では、LLMがコード生成時に陥る5つの構造的弱点を分析し、TDDがそれぞれに対してどのような解を提供するのかを明らかにする。

LLMの「テスト後付けバイアス」という根本問題

LLMのコード生成における最も深刻な問題は、「テスト後付けバイアス(Post-hoc Test Bias)」と呼ばれる傾向である。これは、LLMが実装コードを先に生成し、その後にテストを「後付け」で生成する際に、テストが「コードが何をすべきか」ではなく「コードが何をしているか」を検証するものになってしまう現象だ。

この問題の根源は、LLMの生成メカニズムにある。LLMは自己回帰モデルであり、直前に生成したトークン列を条件として次のトークンを予測する。つまり、実装コードを生成した直後にテストを書かせると、モデルはその実装コードの「パターン」を条件としてテストを生成する。結果として、テストは実装の忠実な「鏡」となり、仕様の「番人」にはならない。

ミューテーションテスティングの研究結果がこの問題を裏付けている。AIが実装後に生成したテストのミューテーションスコアは、人間がTDDで書いたテストと比較して平均40%低い。ミューテーションスコアとは、コードに意図的な変異(バグ)を挿入した際に、テストがその変異を検出できる割合を示す指標である。スコアが低いということは、テストが表面的な検証しか行っておらず、実際のバグを見逃す可能性が高いことを意味する。

さらに厄介なのが「グリーンバー・イリュージョン」と呼ばれる現象だ。AIが生成したテストスイートは全てパスする(グリーンバー)。開発者はこれを見て安心するが、実際にはテストがバグを検出する能力を持っていない。テストが全てパスすることと、テストが品質を担保していることは、全く別の問題なのだ。

TDDはこの問題を構造的に解決する。テストを先に書くということは、実装が存在しない状態でテストを書くということだ。LLMは実装コードを参照できないため、仕様や要件定義に基づいてテストを生成せざるを得ない。これにより、テストは「コードがどう動いているか」ではなく「コードがどう動くべきか」を検証するものになる。

コンテキスト汚染——単一ウィンドウで起きる品質崩壊

LLMのコンテキストウィンドウは、一見すると巨大に見えるが、実際には深刻な制約を抱えている。Claude Codeでの開発では、ファイルの読み取り、ツールの実行結果、エラーメッセージ、そして対話履歴が全て同一のコンテキストウィンドウに蓄積される。テストと実装が同一のコンテキストに存在するとき、LLMは無意識のうちに「カンニング」を行う。

具体的なシナリオを考えてみよう。ある関数に対して3つのエッジケースをテストする必要があるとする。開発者がClaude Codeに「この関数のテストを書いて、次に実装して」と指示する。LLMはまずテストを生成するが、その生成過程で既に「どう実装するか」を内部的に計画している。テストの生成と実装の計画が同時並行で行われるため、テストは無意識のうちに「自分が実装しやすい形」に歪められる。3つのエッジケースのうち、LLMが実装を想定できなかった1つは、テストから「静かに」除外される。

コンテキストウィンドウが埋まるにつれて、この問題はさらに悪化する。LLMは最新のトークンに対してより高い注意を向ける傾向がある(Recency Bias)。プロジェクトが成長し、コンテキストに多くのファイルが読み込まれると、初期に定義したテスト仕様が「圧縮」され、重要度が下がる。結果として、後半に生成されるテストは初期の仕様から乖離していく。

この「コンテキスト汚染」は、単一のClaude Codeセッション内でテストと実装を同時に扱う限り、原理的に避けられない。後述するマルチエージェントアーキテクチャ(第6章)は、この問題に対する構造的な解決策を提供する。テスト作成エージェントと実装エージェントのコンテキストを物理的に分離することで、コンテキスト汚染を根本から排除するのだ。

Kent Beckの原則がAIコーディングで再定義される理由

Kent Beckが2002年に著した「Test-Driven Development: By Example」は、TDDのバイブルとして広く知られている。同書で提唱された核心的な原則は「Clean code that works(動作するきれいなコード)」であった。この原則は、AIコーディングの文脈において、むしろその重要性を増している。

Beckの原則の核心は、「まず動くコードを書き、次にきれいにする」という二段階のアプローチにある。これは人間の開発者にとっては認知負荷を軽減するための戦略だった。一度に「正しさ」と「美しさ」の両方を追求するのではなく、まず「正しさ」を担保し、その安全網の中で「美しさ」を追求する。

LLMにとって、この分離はさらに重要な意味を持つ。LLMはプロンプトの指示が複合的になるほど、各指示への遵守率が低下する。「正しく動作し、きれいで、パフォーマンスも良いコードを書いて」という指示は、各要素が互いに干渉し合い、結果としてどの要素も中途半端になる。TDDの3フェーズ分離(RED-GREEN-REFACTOR)は、LLMに対して「今はこれだけに集中しろ」という明確な指示を与える枠組みとして機能する。

さらに、Beckが強調した「テストを先に書くことで設計が改善される」という効果も、AIコーディングで増幅される。テストを先に書くということは、関数のインターフェース(入力と出力)を先に定義するということだ。LLMにとって、「この入力に対してこの出力を返す関数を実装しろ」という指示は、「いい感じの関数を書いて」という指示よりも遥かに明確である。TDDは、LLMに対する最も効果的なプロンプトエンジニアリング手法でもあるのだ。

Beckのもう一つの重要な原則である「Fake it till you make it」——最初はハードコードされた値を返し、テストが増えるにつれて一般化する――も、LLMの過剰実装傾向への対策として再評価されている。LLMは要求されていない機能まで先回りして実装しようとする。この傾向はYAGNI(You Aren’t Gonna Need It)原則の違反であり、テストでカバーされていないコードパスを生み出す。「テストが要求する振る舞いだけを実装せよ」というTDDの規律は、LLMの過剰実装を効果的に抑制する。

従来開発 vs TDD vs AI×TDD——3つのパラダイム比較

ソフトウェア開発のパラダイムは、「従来型開発」「TDD」「AI×TDD」の3段階で進化してきた。それぞれのパラダイムにおけるワークフロー、品質保証の仕組み、そして限界を比較することで、AI×TDDの位置づけが明確になる。

従来開発 vs TDD vs AI×TDD 開発フロー比較従来開発設計コーディングテストデバッグリリース直線的・手戻りが多いTDDREDテスト作成GREEN最小実装REFACTORリファクタリング繰返リリース反復的・品質保証AI × TDDCLAUDE.md 設定REDAIがテスト作成GREENAIが最小実装自動検証(Hook)REFACTORAIリファクタ自動繰返CI/CD パイプラインリリース完全自動化・高速・高品質自動化レイヤーがTDDを強化

従来型開発では、要件定義、設計、実装、テスト、デバッグという直線的なフローを辿る。テストは実装の後に書かれ、主に「動作確認」として機能する。このアプローチの最大の問題は、テストが「後付け」であるため、実装の内部構造に依存したテストになりやすいことだ。また、テストの網羅性は開発者の経験と注意力に依存する。

TDDでは、テストが先行し、実装はテストを満たすための手段として位置づけられる。Red-Green-Refactorの3フェーズサイクルにより、各ステップで「今何をすべきか」が明確になる。テストは仕様の形式的な表現として機能し、リグレッションの検出精度が高い。一方で、人間がTDDを厳格に実践するには認知的なコストが高く、チーム全体での浸透が難しいという課題がある。

AI×TDDは、TDDの原則をClaude Codeの自動化機能で強制するアプローチだ。CLAUDE.mdでルールを定義し、Hooksでゲートを設け、Skillsでワークフローを自動化することで、TDDの認知コストを大幅に削減する。さらに、マルチエージェントアーキテクチャによるコンテキスト分離は、人間のTDDでは実現できなかった「テスト作成者と実装者の完全な情報隔離」を可能にする。

以下の表は、LLMが持つ構造的弱点と、TDDがそれぞれに対して提供する対策をまとめたものである。

弱点 説明 TDDによる対策
テスト後付けバイアス 実装を先に生成し、テストをそれに合わせて作成する傾向。テストが「コードが何をしているか」を追認するだけの形式的なものになる テストを先に書くことで、実装に依存しない仕様ベースのテストを強制する。テストはRED(失敗)状態から始まるため、後付けバイアスが構造的に排除される
コンテキスト汚染 テストと実装が同一コンテキストウィンドウに存在するとき、LLMが実装の知識をテストに漏洩させる。テストが実装に無意識に迎合する マルチエージェント分離により、テスト作成エージェントと実装エージェントのコンテキストを物理的に隔離。テスト作成者は実装を「知らない」状態でテストを書く
ハルシネーション 存在しないAPIやメソッドの呼び出し、誤ったライブラリの使用法など、事実と異なるコードを自信を持って生成する テスト実行による即座のフィードバック。ハルシネーションで生成されたコードはテストで検出され、LLMは修正を迫られる。Hooksの自動テスト実行で検出を即時化
過剰実装(YAGNI違反) 要求されていない機能やエッジケース処理を先回りして実装し、テストでカバーされていないコードパスを生む 「テストが要求する振る舞いだけを実装せよ」という原則により、テストに書かれていない機能は実装の対象外。GREENフェーズの「最小実装」規律で過剰実装を抑制
エッジケース見落とし 正常系のパターンに偏った学習データにより、異常系やエッジケースのテストが不十分になる。境界値、null、空文字列、大量データなどの考慮不足 REDフェーズで明示的にエッジケースのテストを列挙させることで、正常系偏重を矯正。テストケース設計をプロンプトで構造化し、網羅性を確保する
リグレッション無視 新機能の実装に集中するあまり、既存機能のテストが破壊されても気づかない。特にコンテキストが長くなった後半で発生しやすい PostToolUse Hookでコード変更後に全テストを自動実行。1つでも失敗があればLLMに即座にフィードバックされ、修正が強制される。テストの全量実行を習慣化

この表が示すように、TDDはLLMの弱点に対して一対一で対策を提供する。しかし、これらの対策が効果を発揮するには、TDDの原則がClaude Codeの動作に組み込まれている必要がある。人間が「テストを先に書いて」と毎回指示するのは非現実的であり、構造的な強制メカニズムが必要だ。次章以降で、その具体的な実装方法を解説していく。



Red-Green-Refactorの本質——AIが従うべき3フェーズの鉄則

Red-Green-Refactorは、TDDの核心をなす3フェーズのサイクルである。このサイクルは単なる「手順」ではなく、ソフトウェアの正しさと品質を同時に達成するための規律体系だ。AIコーディングにおいて、この3フェーズの分離はさらに重要な意味を持つ。各フェーズでLLMに異なる「思考モード」を強制することで、コンテキスト汚染を軽減し、生成されるコードの品質を劇的に向上させる。本章では、各フェーズの本質と、Claude Codeで実践する際の具体的なテクニックを解説する。

Red-Green-Refactor サイクル1REDテストを書く(失敗)期待する振る舞いを定義2GREEN最小実装(成功)テストを通す最小のコード3REFACTORリファクタリングテストを保持したまま改善失敗→実装成功→改善改善→次テストONE test → ONE implementation→ repeat各フェーズは厳密に分離 — 一度に一つのことだけに集中

REDフェーズ——失敗するテストを「先に」書く意味

REDフェーズの本質は、「失敗するテストを書く」ことではない。「実装が存在しない状態で、期待する振る舞いを形式化する」ことだ。この微妙だが重要な違いを理解することが、TDDの効果を最大化する鍵となる。

Claude Codeに対してREDフェーズを実行させる際、最も効果的な指示は「この関数のテストを書いて」ではなく、「この要件を満たすテストを書いて、まだ実装は書かないで」という形式だ。前者は暗黙的に「関数が存在する」ことを前提としており、LLMは無意識のうちに実装の詳細を推測しながらテストを書いてしまう。後者は明確に実装の非存在を前提としており、LLMは仕様にのみ基づいてテストを設計する。

REDフェーズで生成されるテストには、以下の特性が必須である。第一に、テストは実行可能でなければならない。コンパイルエラーではなく、アサーションの失敗でテストが赤くなる必要がある。つまり、関数のシグネチャ(型定義やインターフェース)は存在し、中身だけが未実装(あるいはエラーをスロー)という状態が理想的だ。第二に、失敗メッセージが明確でなければならない。「Expected 13500 to be undefined」ではなく「Expected calculateDiscount(15000) to return 13500」のような、意図が読み取れるメッセージが望ましい。

Claude Codeでこれを実現するためのテクニックがある。テスト対象の関数を先に型定義だけ作成し、実装をthrow new Error('Not implemented')にしておく方法だ。こうすることで、テストは正しくインポートでき、実行時には必ず失敗する。LLMに対しては「まずインターフェース定義と空の実装を作成し、次にテストを作成し、テストが失敗することを確認せよ」と段階的に指示する。

GREENフェーズ——最小実装の規律をAIに守らせる方法

GREENフェーズは、TDDにおける最も「退屈な」フェーズであると同時に、最も規律が必要なフェーズでもある。目標は明確だ。テストをパスさせる最小限のコードを書くこと。「最小限」とは、テストが要求する振る舞い以上のことは一切行わないという意味である。

LLMはこのフェーズで最も暴走しやすい。膨大なコードコーパスから学習したLLMは、「良いコード」のパターンを多数記憶しており、テストが1つしかパスしていない段階でも、将来必要になるであろうエラーハンドリング、ロギング、バリデーション、パフォーマンス最適化を先回りして実装しようとする。これはYAGNI原則への明確な違反であり、テストでカバーされていないコードパスを生む。

Claude Codeに最小実装を守らせるために、CLAUDE.mdに以下のような明示的な制約を記述する必要がある。「GREENフェーズでは、テストをパスさせるために必要なコード以外は一切書かないこと。エラーハンドリング、ログ出力、パフォーマンス最適化、コメントは、対応するテストが存在しない限り追加しないこと」。この制約は冗長に見えるかもしれないが、LLMに対する指示は明示的であるほど効果が高い。

Kent Beckが提唱した「Fake It Till You Make It」戦略は、GREENフェーズの規律を体現している。最初のテストに対しては、ハードコードされた値を返すだけでよい。2つ目のテストが追加されて初めて、条件分岐を導入する。3つ目のテストで一般化する。この漸進的なアプローチは、LLMにとっても有効だ。一度に全てのテストをパスさせようとするのではなく、1テストずつ通していくことで、各ステップの変更量が小さくなり、デバッグが容易になる。

実践的なテクニックとして、GREENフェーズのプロンプトに「テスト結果を確認し、失敗しているテストを1つだけ選び、そのテストをパスさせる最小限の変更を行え」という指示を含める方法がある。「全てのテストをパスさせろ」という指示よりも、LLMの出力を小さく制御できる。

REFACTORフェーズ——テストが通った後のクリーンアップ戦略

REFACTORフェーズは、TDDの3フェーズの中で最もAIの能力が活きるフェーズである。全てのテストがパスしている状態(グリーンバー)を安全網として、コードの品質を改善する。重複の除去、命名の改善、関数の分割、マジックナンバーの定数化——これらのリファクタリングパターンの認識と適用は、LLMが最も得意とする領域だ。

しかし、REFACTORフェーズにも重要な規律がある。それは「振る舞いを変えない」ことだ。リファクタリングの定義は「外部から観察可能な振る舞いを変えずにコードの内部構造を改善すること」である。テストが全てパスし続けることが、この定義を満たしていることの証拠となる。

Claude Codeでリファクタリングを行う際、PostToolUse Hookを設定してコード変更後に自動的にテストを実行する仕組みが効果的だ(詳細は第5章で解説)。リファクタリングの各ステップでテストが実行され、万が一テストが失敗した場合はLLMが即座にフィードバックを受け取り、変更を修正できる。

REFACTORフェーズでLLMに適用させるべきリファクタリングパターンは、以下の優先順位で実施するのが効果的だ。まず、コードの重複除去(DRY)。同じロジックが2箇所以上に存在する場合、関数として抽出する。次に、命名の改善。変数名や関数名が意図を正確に表現しているか確認し、より明確な名前に変更する。そして、関数の分割(SRP)。1つの関数が複数の責務を持っている場合、単一責務の関数に分割する。最後に、マジックナンバーの定数化。コード中に散在するリテラル値を、意味のある定数名に置き換える。

注意すべきは、REFACTORフェーズで新しいテストを追加しないことだ。新しいテストの追加はREDフェーズの役割であり、フェーズの混在はTDDの規律を破壊する。もしリファクタリング中に新たなテストケースの必要性に気づいた場合は、それを記録しておき、次のTDDサイクルのREDフェーズで追加する。

「ONE test → ONE implementation → repeat」の原則

TDDにおいて最も重要でありながら最も破られやすい原則が、「1テスト、1実装」の原則である。この原則は、一度に1つのテストだけを追加し、そのテストをパスさせる実装を書き、リファクタリングした後に次のテストに進む、というサイクルを定めている。

LLMは、複数のテストを一度に生成する強い傾向がある。これは学習データに含まれるテストファイルの多くが完成形であり、複数のテストケースが一度に書かれているためだ。しかし、複数のテストを一度に追加すると、GREENフェーズで大量のコードを一度に書く必要が生じ、「最小実装」の規律が崩壊する。

Claude Codeでこの原則を守るための実践的な方法は、プロンプトの段階的な発行である。まず「calculateDiscount関数について、最初のテストケース(10000円超の注文に10%割引を適用する)だけを書いて」と指示する。テストが失敗することを確認した後、「このテストをパスさせる最小限の実装を書いて」と指示する。テストがパスしたら「リファクタリングの余地はあるか」と確認し、次のテストケースに進む。

この「1テスト1実装」のサイクルは、一見すると非効率に見える。しかし、以下の理由から、トータルの開発効率は向上する。第一に、各ステップの変更量が小さいため、バグが混入した場合の原因特定が容易になる。第二に、LLMのコンテキストウィンドウの使用量が各ステップで最小化されるため、コンテキスト汚染のリスクが低下する。第三に、各テストが独立して成功していることが確認できるため、テスト間の依存関係が排除される。

以下のコード例は、calculateDiscount関数のTDDサイクルを3フェーズに分けて示したものである。REDフェーズでテストを書き、GREENフェーズで最小実装を行い、REFACTORフェーズでコードの品質を改善する流れを確認してほしい。

// ============================================
// RED Phase(テスト作成)
// ============================================
// test/calculateDiscount.test.ts
import { describe, it, expect } from 'vitest'
import { calculateDiscount } from '../src/calculateDiscount'

describe('calculateDiscount', () => {
  it('should apply 10% discount for orders over 10000 yen', () => {
    expect(calculateDiscount(15000)).toBe(13500)
  })

  it('should not apply discount for orders under 10000 yen', () => {
    expect(calculateDiscount(5000)).toBe(5000)
  })

  it('should throw for negative amounts', () => {
    expect(() => calculateDiscount(-1)).toThrow('Amount must be positive')
  })
})

// ============================================
// GREEN Phase(最小実装)
// ============================================
// src/calculateDiscount.ts
export function calculateDiscount(amount: number): number {
  if (amount < 0) throw new Error('Amount must be positive')
  if (amount > 10000) return amount * 0.9
  return amount
}

// ============================================
// REFACTOR Phase(改善)
// ============================================
// src/calculateDiscount.ts
const DISCOUNT_THRESHOLD = 10000
const DISCOUNT_RATE = 0.1

export function calculateDiscount(amount: number): number {
  if (amount < 0) {
    throw new Error('Amount must be positive')
  }

  const discount = amount > DISCOUNT_THRESHOLD ? DISCOUNT_RATE : 0
  return amount * (1 - discount)
}

このコード例で注目すべきは、GREENフェーズの実装がいかに素朴であるかだ。if (amount > 10000) return amount * 0.9という実装は、マジックナンバーを含み、条件式が直接的で、コードの意図が明確とは言えない。しかし、GREENフェーズではこれで十分だ。テストがパスすることだけが目標であり、コードの美しさはREFACTORフェーズで追求する。

REFACTORフェーズでは、マジックナンバーが定数化され、割引計算のロジックが統一的な式に書き換えられている。テストは一切変更されておらず、全てのテストがパスし続けている。この「テストはそのまま、実装だけ改善」という制約が、リファクタリングの安全性を担保している。



CLAUDE.mdでTDDを制度化する——ルール設定の実践テンプレート

CLAUDE.mdは、Claude Codeの振る舞いを定義する「憲法」のようなファイルである。プロジェクトのルートに配置されたCLAUDE.mdは、Claude Codeが起動するたびに自動的に読み込まれ、全ての行動の基盤となる。TDDの原則をこのファイルに明示的に記述することで、開発者が毎回「テストを先に書いて」と指示する必要がなくなり、TDDがプロジェクトの制度として機能するようになる。本章では、TDDを制度化するためのCLAUDE.mdテンプレートを、実践的な観点から設計していく。

TDDセクションの基本テンプレート

CLAUDE.mdにTDDルールを記述する際の最も重要な原則は、「曖昧さの排除」である。LLMは曖昧な指示に対して独自の解釈を行い、その解釈が開発者の意図と一致する保証はない。「テストを書いてください」ではなく、「Vitestを使用し、AAAパターンに従い、テストファイルはtest/ディレクトリに*.test.tsの命名規則で作成すること」と具体的に記述する必要がある。

テンプレートの配置場所も重要だ。CLAUDE.mdの先頭に近い位置にTDDセクションを配置することで、LLMの注意度が高い状態でルールが読み込まれる。LLMはコンテキストの先頭と末尾に対してより高い注意を払う傾向があるため(Primacy-Recency Effect)、TDDのような重要なルールは先頭付近に配置すべきだ。

以下に、TDDセクションの完全なテンプレートを示す。このテンプレートはTypeScript + Vitestプロジェクトを前提としているが、フレームワーク依存の部分を差し替えることで、Jest、pytest、JUnit等にも適用可能だ。

## TDD (テスト駆動開発) ルール

### 基本原則
- **テストファースト**: すべての実装はテストを先に書いてから行う
- **Red-Green-Refactor**: このサイクルを厳密に守る
- **1テスト1実装**: 一度に1つのテストだけを追加し、それを通す実装を書く

### テスト実行コマンド
- ユニットテスト: `npm run test`
- 特定ファイル: `npm run test -- path/to/file.test.ts`
- カバレッジ: `npm run test:coverage`

### テスト命名規則
- describe: 対象の関数名またはクラス名
- it/test: `should [期待する振る舞い] when [条件]`
- 例: `it('should return 0 when input is empty string')`

### AAAパターン(必須)
すべてのテストは以下の構造に従う:

// Arrange(準備)
const input = createTestInput()

// Act(実行)
const result = targetFunction(input)

// Assert(検証)
expect(result).toBe(expected)

### 禁止事項
- テストを書く前に実装コードを書くこと
- テストを修正して実装に合わせること(実装を修正せよ)
- `test.skip()` や `test.todo()` を残したままにすること
- テストファイルで `any` 型を使うこと
- 1つのテストで複数の振る舞いを検証すること
- 実装の内部詳細に依存するテストを書くこと

### カバレッジ要件
- 新規コード: 80%以上(行カバレッジ)
- クリティカルパス(決済、認証): 95%以上
- カバレッジが低下するPRはマージしない

### モック方針
- 外部APIのみモック可(内部モジュールは実際のコードを使う)
- Dependency Injectionパターンを優先
- モック対象は明示的にコメントで理由を記述

テスト命名規則とAAAパターンの強制

テストの命名規則は、テストの可読性とメンテナンス性に直結する。LLMが生成するテストの命名には顕著な傾向がある。学習データに含まれる多様なスタイルの影響を受け、同一プロジェクト内で命名規則が統一されないことが多い。あるテストではit('calculates discount')、別のテストではit('should return discounted price when amount exceeds threshold')のように、粒度や形式がばらつく。

推奨する命名規則は、should [期待する振る舞い] when [条件]の形式だ。この形式には2つの利点がある。第一に、テストが「何を検証しているか」が名前から読み取れる。第二に、テストが失敗した際のレポートが自然言語として読みやすくなる。FAIL: calculateDiscount > should apply 10% discount when order exceeds 10000 yenというレポートは、開発者が問題を即座に理解できる。

AAAパターン(Arrange-Act-Assert)は、テストの内部構造を統一するための規則である。Arrangeでテストデータを準備し、Actで対象の関数を実行し、Assertで結果を検証する。この3ステップの分離は、テストの可読性を劇的に向上させる。

LLMは、AAAパターンを指示されてもショートカットしようとする傾向がある。特に単純なテストでは、Arrange・Act・Assertを1行にまとめてexpect(calculateDiscount(15000)).toBe(13500)と書きがちだ。これは短くて読みやすいように見えるが、テストが複雑になるにつれて一貫性が失われる。CLAUDE.mdでAAAパターンを「必須」と明記し、「たとえ1行で書けるテストでもAAAパターンに従うこと」と補足することで、この傾向を矯正できる。

日本語プロジェクトでは、テストの記述言語についても方針を決める必要がある。describeは関数名(英語)、itの記述は日本語で書く方式が実践的だ。例えばdescribe('calculateDiscount', () => { it('10000円超の注文に10%割引を適用すること', ...)のように。これにより、テスト実行結果のレポートが日本語話者にとって読みやすくなる。

禁止事項リスト——AIが暴走しないためのガードレール

LLMの行動を制御する最も効果的な方法は、「やるべきこと」を列挙するよりも「やってはいけないこと」を明示することだ。人間の交通法規が「制限速度60km/h」よりも「速度超過禁止」として認識されるのと同じ原理である。CLAUDE.mdの禁止事項リストは、LLMの行動に対するガードレールとして機能する。

TDDにおいて最も重大な違反は、「テストを修正して実装に合わせる」行為だ。LLMは、テストが失敗した際に「テストを修正する」という選択肢と「実装を修正する」という選択肢を等しい確率で選択しようとする。しかし、TDDにおいてテストは仕様の形式化であり、テストの修正は仕様の変更を意味する。テストが失敗した場合、修正すべきは常に実装であり、テストではない。この原則をCLAUDE.mdに明示的に記述し、「テストが失敗した場合、テストファイルを編集するのではなく、実装ファイルを修正すること。テストの修正が必要な場合は、まず開発者に確認を取ること」と規定する。

test.skip()test.todo()を残したままにすることの禁止も重要だ。LLMは、テストが通らない場合にスキップして先に進もうとする傾向がある。これは人間の開発者でもよく見られる行動だが、TDDの文脈ではテストスイートの信頼性を損なう。スキップされたテストは「未検証の仕様」を意味し、それが蓄積するとテストスイート全体の信頼性が崩壊する。

any型の使用禁止は、TypeScriptプロジェクトにおいて特に重要だ。テストファイルでの型安全性は、テストが正しいデータ構造を使用していることを保証する。LLMは、型のミスマッチに遭遇した際にas anyでキャストして回避しようとすることがあるが、これはテストの信頼性を根本的に損なう。型エラーは「テストデータと実際のインターフェースの不一致」を示す重要なシグナルであり、anyでキャストするのではなく、テストデータを正しい型に修正すべきだ。

実装の内部詳細に依存するテストの禁止も明記すべきだ。例えば、プライベートメソッドの呼び出し回数を検証するテストや、特定の内部変数の値を検証するテストは、実装のリファクタリングに対して脆い。テストはパブリックなインターフェースを通じて振る舞いを検証すべきであり、「どう実装されているか」ではなく「何を返すか・何を行うか」に焦点を当てるべきだ。

モノレポ・マルチプロジェクトでのCLAUDE.md設計

実際の開発現場では、単一のリポジトリに複数のプロジェクトが共存するモノレポ構成が一般的になりつつある。モノレポでは、パッケージごとにテストフレームワークや設定が異なることがあり、単一のCLAUDE.mdでは対応しきれない。

Claude Codeは、CLAUDE.mdの階層構造をサポートしている。リポジトリのルートに配置されたCLAUDE.mdが全体の共通ルールを定義し、各パッケージディレクトリに配置されたCLAUDE.mdがパッケージ固有のルールを上書き・追加する。この仕組みを活用して、TDDルールを階層的に設計する。

ルートのCLAUDE.mdには、全パッケージに共通するTDDの基本原則を記述する。テストファースト、Red-Green-Refactor、AAAパターン、禁止事項リストなど、フレームワークに依存しないルールがここに属する。パッケージレベルのCLAUDE.mdには、フレームワーク固有の設定を記述する。例えば、packages/api/のCLAUDE.mdにはVitestの設定とAPIテストのパターン(スーパーテストの使用法など)を、packages/web/のCLAUDE.mdにはJest + React Testing Libraryの設定とコンポーネントテストのパターンを記述する。

この階層構造により、モノレポ全体でTDDの基本規律を統一しつつ、各パッケージの技術的特性に応じたカスタマイズが可能になる。新しいパッケージを追加する際には、ルートのCLAUDE.mdが自動的に適用されるため、TDDの基本原則が自動的に継承される。パッケージ固有のCLAUDE.mdを追加するのは、フレームワーク固有のルールが必要になった段階で十分だ。

注意すべきは、パッケージレベルのCLAUDE.mdがルートのCLAUDE.mdと矛盾しないようにすることだ。パッケージレベルで「テスト後付けも許可する」のような、ルートの原則に反するルールを記述してはならない。パッケージレベルのCLAUDE.mdは、ルートのルールを「具体化」するものであり、「緩和」するものではない。



Claude Code SkillsでTDDを自動発動させる——SKILL.mdの設計と実装

CLAUDE.mdによるルール定義は、TDDの「法律」を定めるものだった。しかし、法律があるだけでは遵守率は上がらない。必要なのは、法律を自動的に適用する「執行メカニズム」だ。Claude Code Skillsは、特定のコンテキストで自動的に発動するプロンプトテンプレートであり、TDDワークフローの自動化において中心的な役割を果たす。本章では、TDD用Skillの設計原則、完全なテンプレート、そしてHooksとの連携による発動率向上のテクニックを解説する。

SKILL.mdの構造とトリガー条件

Claude Code Skillsは、.claude/skills/ディレクトリに配置されるMarkdownファイルとして定義される。各Skillファイルには、フロントマター(YAML形式のメタデータ)とプロンプト本文が含まれる。フロントマターにはSkillの名前、説明、そしてトリガー条件が記述される。

トリガー条件は、ユーザーのプロンプトに含まれるキーワードやフレーズに基づいて評価される。TDD Skillの場合、「実装して」「機能を追加」「バグを修正」「新しいAPIを作成」といった、コード変更を伴うフレーズがトリガーとなる。英語と日本語の両方のトリガーフレーズを設定することで、バイリンガル環境にも対応できる。

トリガー条件の設計で重要なのは、「偽陽性」と「偽陰性」のバランスだ。トリガーが広すぎると、コード変更を伴わないタスク(ドキュメント更新、設定変更など)でもTDD Skillが発動し、不要なオーバーヘッドが生じる。トリガーが狭すぎると、コード変更タスクでSkillが発動せず、TDDが適用されない。実務的には、やや広めに設定しておき、Skill本文の冒頭で「このタスクがコード実装を含まない場合は、このワークフローをスキップしてよい」という脱出条件を記述するのが効果的だ。

以下に、TDD統合Skillの完全なテンプレートを示す。

---
name: tdd-workflow
description: テスト駆動開発ワークフロー
trigger: implement|add feature|fix bug|create|build|修正|実装|追加|作成|構築
---

# TDD ワークフロー

あなたはテスト駆動開発(TDD)の厳格な実践者です。
すべてのコード変更は以下のサイクルに従ってください。

## Phase 1: RED(テスト作成)
1. 要件を分析し、テストケースを設計する
2. 失敗するテストを1つ書く
3. テストを実行して失敗を確認する(`npm run test`)
4. 失敗メッセージが期待通りであることを確認する

## Phase 2: GREEN(最小実装)
1. テストをパスさせる最小限のコードを書く
2. "最小限"とは:余分な機能、最適化、リファクタリングを含まない
3. テストを実行してパスを確認する
4. 他のテストが壊れていないことを確認する

## Phase 3: REFACTOR(改善)
1. コードの重複を除去する
2. 命名を改善する
3. 関数を適切なサイズに分割する
4. テストを実行して全てパスすることを確認する

## 制約
- Phase 1を完了するまでPhase 2に進まない
- Phase 2を完了するまでPhase 3に進まない
- 各Phaseでテスト実行を必ず行う
- テストが失敗したまま次のテストを追加しない

TDD統合Skillの完全テンプレート

上記の基本テンプレートは、TDDの3フェーズを明確に定義しているが、実際のプロジェクトではさらに詳細なガイダンスが必要になる。特に、各フェーズでLLMが陥りやすい典型的なミスを防ぐための追加ルールが重要だ。

完全テンプレートには、以下の要素を追加する。まず、各フェーズの「完了条件」を明示する。REDフェーズの完了条件は「テストが実行でき、期待通りに失敗すること」であり、「テストファイルが作成されたこと」ではない。GREENフェーズの完了条件は「全てのテストがパスすること」であり、「コードが書かれたこと」ではない。この違いは微妙だが、LLMの行動に大きな影響を与える。

次に、フェーズ間の遷移ルールを定義する。REDフェーズからGREENフェーズへの遷移は、「テスト実行の結果を出力として示し、失敗していることを確認してから」行う。口頭での確認ではなく、実際のテスト実行結果をエビデンスとして要求することで、LLMがフェーズをスキップする可能性を排除する。

さらに、エラーハンドリングのルールも追加する。テスト実行中にコンパイルエラーが発生した場合は、アサーションの失敗ではないため、REDフェーズは未完了とみなす。まずコンパイルエラーを解消し、テストが「正しく失敗する」状態にしてからGREENフェーズに進む。この区別は重要だ。コンパイルエラーによる失敗は「テストが仕様を表現できていない」ことを意味し、アサーションの失敗は「テストが仕様を正しく表現しているが、実装がまだ追いついていない」ことを意味する。

自動トリガーフレーズの設定

Skillのトリガーフレーズは、正規表現パターンとして定義される。TDD Skillの場合、以下の2つのカテゴリに分けてトリガーを設計するのが効果的だ。

第一のカテゴリは「実装系」のトリガーだ。「implement」「add feature」「create」「build」「実装」「追加」「作成」「構築」などのフレーズが含まれる場合、新規コードの作成を伴う可能性が高く、TDD Skillを発動すべきだ。第二のカテゴリは「修正系」のトリガーだ。「fix bug」「repair」「resolve」「修正」「バグ修正」「不具合対応」などのフレーズが含まれる場合、既存コードの変更を伴うため、回帰テストの作成を含むTDDワークフローが必要になる。

トリガーフレーズの設計で注意すべきは、LLMが処理する自然言語の多様性だ。開発者が「ユーザー登録のAPIを作って」と指示する場合と「ユーザー登録機能を実装して」と指示する場合では、同じ意図でも使用されるフレーズが異なる。可能な限り多くのバリエーションをトリガーに含めることで、Skillの発動漏れを防ぐ。

ただし、トリガーの網羅性だけでは限界がある。開発者が「ユーザーの一覧を返すようにして」のように、明示的な実装キーワードを含まない指示を行う場合、トリガーが反応しない可能性がある。この問題に対する解決策が、次に説明するHook連携だ。

Skill活性化率を20%から84%に引き上げるHook連携

Skillsのトリガーメカニズムだけに依存した場合、実際の発動率は約20%程度にとどまることが経験的に知られている。これは、開発者のプロンプトが必ずしもトリガーフレーズに合致しないためだ。この発動率を大幅に向上させるのが、UserPromptSubmit Hookとの連携である。

コンテキスト汚染 Before / AfterBefore: 単一コンテキストコンテキストウィンドウテスト作成実装コードリファクタデバッグコンテキスト汚染テストが実装に影響されるAfter: Skill 分離SKILLTDD テスト作成独立コンテキスト — 実装を知らないSKILL実装テストだけを受け取り最小実装SKILLリファクタテストをガードレールとして改善関心の分離正直なテスト — 実装に引きずられない

UserPromptSubmit Hookは、開発者がプロンプトを送信するたびに発動するHookだ。このHookでTDD Skillの内容を読み込み、コンテキストに注入することで、トリガーフレーズに依存せずにTDDワークフローを強制できる。

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "cat .claude/skills/tdd-workflow.md 2>/dev/null || echo 'No TDD skill found'"
          }
        ]
      }
    ]
  }
}

この設定により、全てのプロンプト送信時にTDD Skillの内容がコンテキストに注入される。matcherが空文字列であるため、全てのプロンプトに対して発動する。結果として、LLMは常にTDDワークフローの指示を「見ている」状態になり、コード変更を伴うタスクでは自動的にTDDサイクルに従う。

ただし、全てのプロンプトに対してSkillを注入することのデメリットもある。コンテキストウィンドウの消費量が増加し、非コードタスク(ファイルの検索、ドキュメントの確認など)でも不要なTDD指示が含まれる。この問題を軽減するために、Hookのコマンドをより洗練された形にすることもできる。例えば、プロンプトの内容を分析し、コード変更を伴う可能性がある場合のみSkillを注入するスクリプトを使用する方法だ。

Skill単独での発動率が約20%であるのに対し、Hook連携を導入すると発動率は約84%まで向上する。残りの約16%は、コード変更を伴わないタスク(ファイルの読み取り、情報の確認など)であり、TDDが適用不要なケースに相当する。つまり、Hook連携により、TDDが適用されるべきケースのほぼ全てでSkillが発動する状態を実現できる。



Hooksによるテスト自動実行——書かなければ通さないゲートの構築

CLAUDE.mdはルールを定義し、Skillsはワークフローをガイドする。しかし、これらはあくまで「指示」であり、強制力はLLMの「意志」に依存する。LLMが指示を無視したり、コンテキストの後半で指示を忘却したりするリスクは常に存在する。Hooksは、この問題に対する決定的な解決策を提供する。ツールの実行前後に自動的にスクリプトを実行し、TDDの規律を機械的に強制する仕組みだ。本章では、PreToolUse、PostToolUse、UserPromptSubmitの3種類のHookを活用したTDDゲートの構築方法を解説する。

PreToolUse Hook——Write/Edit操作前のテスト検証

PreToolUse Hookは、Claude Codeがツール(Write、Edit、Bash等)を実行する直前に発動する。このHookを活用して、ソースファイルへの書き込み前にテストファイルの存在を検証するゲートを構築する。

ゲートの基本ロジックはシンプルだ。Claude Codeがsrc/ディレクトリ配下のファイルを作成・編集しようとした場合、対応するテストファイルがtest/ディレクトリに存在するかを確認する。テストファイルが存在しない場合、Hookは非ゼロの終了コードを返し、エラーメッセージを出力する。Claude Codeはこのエラーを受け取り、まずテストファイルを作成するよう促される。

このアプローチには重要な設計上の考慮事項がある。テストファイルの「存在確認」だけでは不十分ではないか、という疑問だ。空のテストファイルを作成してゲートを通過し、実装を先に書くことは技術的に可能だ。しかし、実際にはこのレベルのチェックで十分な効果がある。LLMはCLAUDE.mdとSkillsでTDDの原則を理解しており、Hookはあくまで「うっかり忘れた場合の安全網」として機能する。意図的にTDDを回避しようとするLLMに対しては、より厳密なチェック(テストファイル内にassertionが含まれるか等)も追加できるが、通常はファイル存在チェックで十分だ。

テストファイルのパスマッピングも重要な設計ポイントだ。src/utils/calculateDiscount.tsに対応するテストファイルはtest/utils/calculateDiscount.test.tsなのか、src/utils/__tests__/calculateDiscount.test.tsなのか、あるいはsrc/utils/calculateDiscount.spec.tsなのか。プロジェクトの規約に合わせてパスマッピングのロジックをカスタマイズする必要がある。以下のスクリプトは、src/test/に置換し、拡張子の前に.testを挿入するシンプルなマッピングを実装している。

#!/usr/bin/env node
const path = require('path')
const fs = require('fs')

const filePath = process.env.CLAUDE_FILE_PATH || ''

// Only check source files, not test files
if (!filePath.includes('/src/') || filePath.includes('.test.') || filePath.includes('.spec.')) {
  process.exit(0)
}

// Derive expected test file path
const parsed = path.parse(filePath)
const testFile = filePath
  .replace('/src/', '/test/')
  .replace(parsed.ext, `.test${parsed.ext}`)

if (!fs.existsSync(testFile)) {
  console.error(`テストファイルが見つかりません: ${testFile}`)
  console.error(`TDDルール: 実装の前にテストを作成してください。`)
  process.exit(1)
}

UserPromptSubmit Hook——プロンプト送信時のSkill強制評価

前章でSkillとの連携について述べたが、ここではUserPromptSubmit Hookの技術的な詳細をさらに掘り下げる。UserPromptSubmit Hookは、開発者がプロンプトを送信した瞬間に発動し、LLMがプロンプトを処理する前にコンテキストに情報を注入する。

TDD強制の観点では、このHookに以下の2つの機能を持たせるのが効果的だ。第一に、TDD Skillの内容をコンテキストに注入する機能。前章で示したとおり、cat .claude/skills/tdd-workflow.mdを実行してSkillの全文をコンテキストに読み込む。第二に、現在のテスト実行状態を表示する機能。既存のテストが全てパスしているか、失敗しているテストがあるかを表示することで、LLMに現在の「出発点」を認識させる。

この2つを組み合わせることで、LLMは全てのプロンプトに対して「TDDモードが有効であり、現在のテスト状態はこうであり、これからの作業はTDDサイクルに従う必要がある」という情報を持った状態でタスクに取り組む。特に、長いセッションの後半でコンテキストウィンドウが圧迫されている場合でも、毎回新鮮なTDD指示が注入されるため、ルールの忘却を防止できる。

PostToolUse Hook——コード変更後の自動テスト実行

PostToolUse Hookは、TDD Hooksの中で最もインパクトの大きい仕組みだ。Claude Codeがファイルの書き込み(Write、Edit)を完了した直後に自動的にテストを実行し、その結果をLLMに即座にフィードバックする。これにより、Red-Green-Refactorの各フェーズで「テスト実行」が自動的に組み込まれる。

PostToolUse Hookの設計で最も重要なのは、テスト実行の範囲とパフォーマンスのバランスだ。全てのテストを毎回実行するのは、小規模プロジェクトでは問題ないが、大規模プロジェクトではテスト実行に数分かかることがあり、開発のリズムを著しく損なう。推奨するアプローチは、変更されたファイルに関連するテストのみを実行し、全テストの実行はREFACTORフェーズの完了時に行うことだ。

以下に、3種類のHookを統合した完全な設定を示す。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "node scripts/check-test-exists.js \"$CLAUDE_FILE_PATH\""
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -q 'src/'; then npm run test -- --reporter=verbose 2>&1 | tail -20; fi"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'TDD Mode Active: Write tests FIRST, then implement.'"
          }
        ]
      }
    ]
  }
}

この設定は以下のように動作する。PreToolUseの段階で、ソースファイルへの書き込みが試みられた場合にテストファイルの存在を確認する。テストファイルが存在しなければ、書き込みがブロックされ、LLMはテストの作成を促される。PostToolUseの段階で、ソースファイルへの書き込みが完了した直後にテストを自動実行し、結果の末尾20行をLLMに表示する。UserPromptSubmitの段階で、全てのプロンプト送信時にTDDモードのリマインダーを表示する。

tdd-guardの導入と設定——完全セットアップ手順

上記のHook設定は手動で行うことも可能だが、より包括的で堅牢なTDDガードを実現するために、専用のツールを導入することも検討に値する。TDDガードの概念をパッケージ化したアプローチでは、テストファイルの存在チェック、テストの自動実行、カバレッジの監視を統合的に管理できる。

TDD Guard フローチャート開発者がプロンプト送信UserPromptSubmit HookTDD Skill 評価No通常フローYesREDテスト作成PreToolUse Hookテストは存在する?Noブロックテスト作成へYesGREEN実装コード作成PostToolUse Hook: テスト自動実行テスト全パス?No修正を要求YesREFACTORリファクタリング

実際のプロジェクトに導入する際のセットアップ手順は以下のとおりだ。まず、scripts/ディレクトリにTDDガード用のスクリプトを配置する。次に、.claude/settings.jsonにHookの設定を記述する。最後に、CLAUDE.mdにTDDルールを記述し、Skillsファイルを配置する。この3つのレイヤー(ルール定義、ワークフロー自動化、実行時強制)が揃って初めて、TDDが「制度」として機能する。

導入後の効果を測定するために、以下のメトリクスを追跡することを推奨する。第一に、テストカバレッジの推移。TDDガード導入前後でカバレッジがどの程度向上したかを定期的に計測する。第二に、テストファースト遵守率。Gitのコミット履歴を分析し、テストファイルが実装ファイルよりも先にコミットされている割合を計測する。第三に、リグレッション発生率。本番環境での不具合報告数を追跡し、TDDガード導入前後での変化を確認する。

これらのメトリクスが改善傾向を示していれば、TDDガードは期待どおりに機能している。改善が見られない場合は、Hookの設定やCLAUDE.mdのルールを見直し、ガードが適切に強制されているかを確認する必要がある。



マルチエージェントTDDアーキテクチャ——3エージェント分離の設計思想

ここまで解説してきたCLAUDE.md、Skills、Hooksによるアプローチは、単一のClaude Codeセッション内でTDDを強制する方法だった。これらは効果的だが、構造的な限界がある。単一のコンテキストウィンドウ内でテストと実装の両方を扱う限り、「コンテキスト汚染」の問題は本質的に解決できない。本章では、この問題に対する根本的な解決策として、3つのサブエージェントによるTDDアーキテクチャを提案する。テスト作成、実装、リファクタリングの各フェーズを物理的に分離されたエージェントに委任することで、「正直なテスト」の生成を構造的に保証する。

なぜ単一コンテキストではTDDが破綻するのか

単一コンテキスト内でのTDDの破綻メカニズムを、より詳細に分析してみよう。LLMは自己回帰モデルであり、全ての生成は「これまでに生成したトークン列」を条件として行われる。テストを生成した後に実装を生成する場合、実装はテストの内容を「見ている」状態で生成される。これは一見すると問題ないように思えるが、逆方向の影響が重要だ。

LLMは次のトークンを予測する際に、コンテキスト全体の情報を利用する。テストを生成する段階で、LLMは既に「この後に実装を書く」という暗黙の計画を持っている。この計画は、テストの生成にも影響を与える。具体的には、LLMは「自分が実装しやすいテスト」を無意識のうちに生成する傾向がある。これは「自己奉仕バイアス(Self-serving Bias)」のAI版とも言える現象だ。

さらに問題なのは、このバイアスが検出困難であることだ。テストは実行でき、一見すると合理的なケースをカバーしている。しかし、ミューテーションテストを実行すると、テストの検出能力が低いことが露呈する。テストは「コードが正しく動いている場合に通る」が、「コードにバグがある場合に落ちる」能力が低い。

マルチエージェントアーキテクチャは、この問題をコンテキストの物理的分離によって解決する。テスト作成エージェントは実装コードを一切見ることができず、実装エージェントはテストの設計意図を知ることができない。この情報の非対称性が、「正直なテスト」と「テストに忠実な実装」を生み出す。

tdd-test-writer(REDエージェント)の役割と制約

tdd-test-writerは、TDDサイクルのREDフェーズを担当するサブエージェントだ。このエージェントの最も重要な制約は、「実装コードを読み取る権限を持たない」ことだ。エージェントがアクセスできる情報は、要件ドキュメント、型定義ファイル(*.d.tstypes.ts)、既存のインターフェース定義のみに限定される。

この制約により、tdd-test-writerが生成するテストは純粋に「仕様ベース」のテストとなる。「この関数は何を受け取り、何を返すべきか」「どのような異常入力に対して、どのようなエラーを返すべきか」「どの境界値でどのような振る舞いをすべきか」——これらの問いに対する回答として、テストが生成される。実装の内部構造に依存したテスト(ホワイトボックステスト)ではなく、入出力の仕様に基づいたテスト(ブラックボックステスト)が自然に生成される。

tdd-test-writerの出力は、テストファイルのパス、テスト実行結果(全てFAILであること)、そして各テストが検証する振る舞いの説明の3つから構成される。テスト実行結果が全てFAILであることの確認は必須だ。もしテストが意図せずパスした場合、それは「実装が既に存在する」か「テストが不正である(常にパスするtautological test)」かのどちらかを意味し、いずれも問題である。

# tdd-test-writer CLAUDE.md
## Role: RED Agent -- テスト作成専門

### 権限
- テストファイルの作成・編集(*.test.ts, *.spec.ts)
- 型定義の読み取り(*.d.ts, types.ts)
- 要件ドキュメントの読み取り
- 実装ファイルの読み取り・編集は禁止(src/**/*.ts ※テスト除く)

### ルール
1. テストは必ず失敗する状態で作成する
2. 実装コードを参照してはならない
3. 要件と型定義のみを根拠にテストを書く
4. テストが意図せずパスした場合は報告する

### 出力形式
- テストファイルのパス
- テスト実行結果(全てFAIL)
- テストが検証する振る舞いの説明

tdd-implementer(GREENエージェント)の最小実装ルール

tdd-implementerは、TDDサイクルのGREENフェーズを担当するサブエージェントだ。このエージェントは、テストファイルと型定義を読み取り、テストをパスさせる最小限の実装を書く。重要な制約は2つある。第一に、テストファイルの編集権限を持たない。第二に、テストが要求しない機能を追加してはならない。

tdd-implementerにとってのテストファイルは「仕様書」として機能する。テストが要求する振る舞いを満たすコードを書くことが唯一の目標であり、それ以上のことは禁じられている。「テストが要求しないエッジケース処理は追加しない」というルールは、YAGNI原則の厳格な適用だ。将来必要になるかもしれない機能は、将来のテストが書かれた時に実装すればよい。

このエージェントの「最小実装」規律は、Kent Beckの「Fake It Till You Make It」戦略に直結する。最初のテストに対しては、ハードコードされた値を返すことすら許容される。複数のテストが存在する場合は、全てのテストをパスさせる必要があるが、それでも「最もシンプルな実装」を選択すべきだ。

# tdd-implementer CLAUDE.md
## Role: GREEN Agent -- 最小実装専門

### 権限
- 実装ファイルの作成・編集
- テストファイルの読み取り
- 型定義の読み取り
- テストファイルの編集は禁止
- テストが要求しない機能の追加は禁止

### ルール
1. テストをパスさせる最小限のコードのみ書く
2. リファクタリングはしない(次のフェーズで行う)
3. テストが要求しないエッジケース処理は追加しない
4. 全テストがパスすることを確認する

### 出力形式
- 変更したファイルのパス
- テスト実行結果(全てPASS)
- 実装の簡潔な説明

tdd-refactorer(REFACTORエージェント)のリファクタリング基準

tdd-refactorerは、TDDサイクルのREFACTORフェーズを担当するサブエージェントだ。このエージェントは3つのエージェントの中で最も広い読み取り権限を持ち、テストファイルと実装ファイルの両方にアクセスできる。ただし、書き込み権限は実装ファイルのみに限定され、テストファイルの編集は禁止されている。

リファクタリングの基準は、以下の5つの観点で評価される。第一に、コードの重複(DRY原則)。同一または類似のロジックが複数箇所に存在する場合、共通関数として抽出する。第二に、単一責任(SRP)。1つの関数が複数の責務を持っている場合、責務ごとに関数を分割する。第三に、命名の明確性。変数名、関数名、定数名が意図を正確に表現しているか確認する。第四に、マジックナンバー。リテラル値が散在している場合、意味のある定数名に置き換える。第五に、循環的複雑度。関数内の条件分岐が多すぎる場合、Early Return、ストラテジーパターン、ポリモーフィズムなどのテクニックで複雑度を低減する。

tdd-refactorerの最も重要なルールは、「リファクタリング後にテストが1つでも失敗したら、変更を元に戻す」ことだ。リファクタリングの定義は「外部から観察可能な振る舞いを変えずにコードの内部構造を改善すること」であり、テストの失敗は振る舞いの変更を意味する。テストがグリーンの状態を維持することが、リファクタリングの正当性を担保する唯一の証拠だ。

# tdd-refactorer CLAUDE.md
## Role: REFACTOR Agent -- コード改善専門

### 権限
- 実装ファイルの編集
- テストファイルの読み取り
- 全ソースコードの読み取り
- テストファイルの編集は禁止
- 新機能の追加は禁止

### ルール
1. すべてのリファクタリング後にテストを実行する
2. テストが1つでも失敗したら変更を元に戻す
3. 以下の観点でリファクタリングする:
   - コードの重複除去(DRY)
   - 関数の単一責任化(SRP)
   - 命名の改善
   - マジックナンバーの定数化
   - 複雑度の低減
4. パフォーマンス最適化は明確なボトルネックがある場合のみ

### 出力形式
- リファクタリング内容の一覧
- テスト実行結果(全てPASS)
- コード品質メトリクスの変化

コンテキスト分離による「正直なテスト」の実現

3エージェントアーキテクチャの核心的な価値は、「正直なテスト」の生成にある。正直なテストとは、仕様に基づいて書かれ、実装の内部構造に依存せず、バグの検出能力が高いテストを指す。

マルチエージェント TDD アーキテクチャオーケストレータータスク分解・フロー制御・結果統合RED エージェントtdd-test-writerテスト設計失敗テスト作成期待値定義独立コンテキスト実装コードを見ないGREEN エージェントtdd-implementer最小実装テストパスYAGNI 原則独立コンテキストテストのみ受領REFACTOR エージェントtdd-refactorerコード改善DRY 原則テスト保持独立コンテキストテスト+実装を受領テストコードコンテキスト分離 = 正直なテスト各エージェントは必要な情報のみ受け取り、独立して判断する

単一エージェントでは、テスト作成者と実装者が同一の「頭脳」を共有しているため、テストは無意識のうちに実装に迎合する。マルチエージェントアーキテクチャでは、tdd-test-writerが実装コードにアクセスできないという物理的制約により、テストは純粋に仕様から導出される。これは、人間のTDDにおける「テスト担当者と実装担当者を分ける」ペアプログラミングの実践を、AIの世界で構造的に再現したものだ。

この「情報の非対称性」は、ミューテーションテストのスコアに直接的な影響を与える。コンテキスト分離されたエージェントが生成するテストは、単一エージェントのテストと比較して、ミューテーションスコアが有意に向上する。これは、テストが「実装がたまたま正しく動いている」ことではなく、「仕様で定義された振る舞いが満たされている」ことを検証しているためだ。

以下の表は、3つのエージェントの責務と制約を比較したものである。

項目 tdd-test-writer tdd-implementer tdd-refactorer
フェーズ RED GREEN REFACTOR
読み取り可能 要件、型定義、インターフェース テストファイル、型定義 全ソースコード
書き込み対象 テストファイルのみ 実装ファイルのみ 実装ファイルのみ
成功条件 テストが失敗すること テストがパスすること テストがパスし品質向上
主要原則 仕様に忠実 YAGNI(最小実装) DRY、SRP

実際にこの3エージェントアーキテクチャを運用する場合、Claude CodeのTaskツール(サブエージェント呼び出し)を活用する。メインのClaude Codeセッションがオーケストレーターとして機能し、各フェーズで適切なサブエージェントにタスクを委任する。オーケストレーターは、各エージェントの出力を確認し、フェーズの完了条件が満たされていることを検証してから次のフェーズに進む。

具体的な運用フローは以下のとおりだ。まず、開発者がメインセッションに「ユーザー登録APIを実装して」と指示する。オーケストレーターは要件を分析し、tdd-test-writerにタスクを委任する。tdd-test-writerは、要件と型定義のみを参照してテストを作成し、テスト実行結果(全てFAIL)を返す。オーケストレーターは結果を確認し、次にtdd-implementerにタスクを委任する。tdd-implementerは、テストファイルと型定義のみを参照して最小実装を行い、テスト実行結果(全てPASS)を返す。最後に、tdd-refactorerにリファクタリングを委任し、コード品質の改善とテストのグリーン状態の維持を確認する。

このアーキテクチャの導入コストは、単一エージェントのアプローチよりも高い。3つのエージェントのCLAUDE.md設定、オーケストレーションロジックの設計、エージェント間の受け渡しプロトコルの定義が必要だ。しかし、プロジェクトの規模が大きくなるほど、コンテキスト分離によるテスト品質の向上が投資に見合うリターンをもたらす。特に、チーム開発で複数の開発者がClaude Codeを使用している場合、マルチエージェントアーキテクチャはテスト品質のばらつきを抑制し、プロジェクト全体のコード品質を底上げする効果がある。

次章以降では、ミューテーションテストによるテスト品質の検証方法、TDDプロンプトエンジニアリング、そして実際のREST APIをゼロからTDDで構築するケーススタディを解説する。

Jest / Vitest ユニットテスト自動生成——フレームワーク別実装ガイド

前章までで、TDDの原則、CLAUDE.mdの設定、Skills/Hooksによる自動化、そしてマルチエージェント構成まで解説した。ここからは、具体的なテストフレームワークとの統合に焦点を移す。「どのフレームワークで」「どのように」テストを書かせるかは、AI×TDDの実用性を大きく左右する。本章では、現代のJavaScript/TypeScriptプロジェクトで主流となるVitestとJestの両方について、Claude Codeとの最適な連携方法を詳解する。

Vitest MCP Serverの導入と設定

Vitestは公式のMCP(Model Context Protocol)Serverを提供しており、Claude Codeとネイティブに統合できる。これはJestにはない大きなアドバンテージだ。MCP Serverを導入すると、Claude Codeがテストの実行、結果の解析、カバレッジの確認をエディタ内から直接行えるようになる。

まず、MCP Serverのインストールから始める。Vitestの最新版(v3.x以降)にはMCPサーバー機能が内蔵されており、--mcpフラグで起動できる。

# Vitestが既にインストールされていれば追加パッケージは不要
npm install -D vitest

# MCP Serverの動作確認
npx vitest --mcp --help

次に、プロジェクトルートの.mcp.jsonファイルにVitest MCP Serverの設定を追加する。

{
  "mcpServers": {
    "vitest": {
      "command": "npx",
      "args": ["vitest", "--mcp"],
      "env": {
        "NODE_ENV": "test"
      }
    }
  }
}

この設定により、Claude Codeは以下の操作をMCP経由で実行できるようになる。

  • テストの実行——特定のテストファイルまたはテストスイート全体を実行し、結果をリアルタイムで取得
  • カバレッジの確認——テスト実行後のカバレッジレポートを解析し、未カバーの行やブランチを特定
  • テストの再実行——コード変更後に影響を受けるテストのみを選択的に再実行
  • エラーの解析——テスト失敗時のスタックトレースやアサーションエラーを構造化された形式で受け取り、修正案を生成

MCP Serverの最大の利点は、Claude Codeがテスト結果を「構造化されたデータ」として受け取れることだ。ターミナル出力のテキストを解析するのではなく、テスト名、ステータス、エラーメッセージ、カバレッジ数値が明確に分離された形で取得できる。これにより、AIの判断精度が大幅に向上する。

Jest用CLAUDE.mdテスト設定テンプレート

JestにはMCP Serverが存在しないため、CLAUDE.mdでの明示的な設定がより重要になる。以下は、Jest環境でClaude Codeが適切にテストを生成するためのCLAUDE.mdテンプレートだ。

# Jest テスト設定

## テストランナー
- フレームワーク: Jest 29.x
- テストコマンド: `npx jest --verbose`
- 単一ファイル実行: `npx jest --verbose path/to/test`
- カバレッジ: `npx jest --coverage`

## ファイル配置
- テストファイル: `src/__tests__/` ディレクトリ内
- 命名規則: `[対象モジュール].test.ts`
- モックファイル: `src/__mocks__/` ディレクトリ内

## Jest固有パターン(必須)
- モック: `jest.mock()` を使用(vi.mock ではない)
- スパイ: `jest.spyOn()` を使用(vi.spyOn ではない)
- フェイクタイマー: `jest.useFakeTimers()` を使用
- モジュールモック: `jest.mock('module-name')` でホイスティングを活用
- 手動モック: `__mocks__/` ディレクトリの手動モックを優先

## Transform設定
- TypeScript: ts-jest を使用
- ESM: jest.config.ts に `transform` 設定が必要
- パスマッピング: `moduleNameMapper` で tsconfig paths を解決

## テスト構造テンプレート
- describe ブロックでモジュール/クラス名をグループ化
- it/test でケースを記述(日本語可)
- beforeEach でテストごとの初期化
- afterEach でクリーンアップ
- beforeAll/afterAll はDB接続等のリソース管理のみ

## 禁止事項
- `jest.setTimeout()` のグローバル変更禁止(各テストで設定)
- `--forceExit` フラグの使用禁止(リソースリークの隠蔽)
- スナップショットテストの安易な使用禁止(意図的な場合のみ)

このテンプレートのポイントは、JestとVitestのAPI差異を明確にしていることだ。Claude CodeはデフォルトでVitest寄りのコードを生成する傾向がある。vi.fn()jest.fn()vi.mock()jest.mock()——これらは似ているが互換性はない。CLAUDE.mdで「Jest固有パターン(必須)」と明記することで、この混同を防止できる。

また、Jestではjest.mock()のホイスティング(モジュールスコープへの自動巻き上げ)が暗黙的に行われるが、VitestではESMの仕様に従い明示的なvi.hoisted()が必要になる場合がある。このようなフレームワーク固有の挙動を理解した上でCLAUDE.mdを設計することが、テスト生成の品質を左右する。

モック戦略——Dependency InjectionでFlakyテストを排除

テスト品質の核心は、モック戦略にある。AIが生成するテストで最も多い問題は「不適切なモック」だ。外部依存を直接参照したコードは、テスト時にモックの差し込みが難しく、結果としてFlakyテスト(不安定なテスト)を生む。

根本的な解決策は、Dependency Injection(DI)パターンをプロダクションコードに適用することだ。DIにより依存関係が明示化され、テスト時のモック差し替えが容易になる。

// テスト困難な直接依存
class OrderService {
  async createOrder(items: Item[]) {
    const total = items.reduce((sum, i) => sum + i.price, 0)
    const payment = await stripe.charges.create({ amount: total }) // 直接依存
    await sendEmail(order.email, 'Order confirmed')  // 直接依存
    return { id: generateId(), total, paymentId: payment.id }
  }
}

// DI によるテスト容易な設計
interface PaymentGateway {
  charge(amount: number): Promise<{ id: string }>
}

interface EmailService {
  send(to: string, subject: string): Promise<void>
}

class OrderService {
  constructor(
    private payment: PaymentGateway,
    private email: EmailService
  ) {}

  async createOrder(items: Item[]) {
    const total = items.reduce((sum, i) => sum + i.price, 0)
    const result = await this.payment.charge(total)
    await this.email.send(order.email, 'Order confirmed')
    return { id: generateId(), total, paymentId: result.id }
  }
}

// テスト
describe('OrderService', () => {
  it('should create order with correct total', async () => {
    // Arrange
    const mockPayment: PaymentGateway = {
      charge: vi.fn().mockResolvedValue({ id: 'pay_123' })
    }
    const mockEmail: EmailService = {
      send: vi.fn().mockResolvedValue(undefined)
    }
    const service = new OrderService(mockPayment, mockEmail)

    // Act
    const order = await service.createOrder([
      { price: 1000 }, { price: 2000 }
    ])

    // Assert
    expect(order.total).toBe(3000)
    expect(mockPayment.charge).toHaveBeenCalledWith(3000)
  })
})

DI パターンには主に3つのバリエーションがある。

1. コンストラクタインジェクション——上記の例のように、コンストラクタで依存を受け取るパターン。最も明示的で、テストの意図が読みやすい。クラスベースのアーキテクチャに適している。

2. ファクトリ関数パターン——関数型プログラミングスタイルに適したパターン。依存をパラメータとして受け取るファクトリ関数を作成する。

// ファクトリ関数パターン
function createOrderService(deps: {
  payment: PaymentGateway
  email: EmailService
}) {
  return {
    async createOrder(items: Item[]) {
      const total = items.reduce((sum, i) => sum + i.price, 0)
      const result = await deps.payment.charge(total)
      await deps.email.send(order.email, 'Order confirmed')
      return { id: generateId(), total, paymentId: result.id }
    }
  }
}

3. パラメータインジェクション——個々の関数に依存をパラメータとして渡すパターン。小規模なユーティリティ関数に適している。

AIにとってDIパターンが重要な理由は明確だ。DIが標準化されたコードベースでは、Claude Codeがテストを生成する際に「何をモックすべきか」が自動的に判明する。インターフェースの型定義から必要なモックの形状が決定され、コンストラクタやファクトリ関数のシグネチャから注入方法が明確になる。結果として、AIが生成するテストの精度が飛躍的に向上する。

カバレッジ分析とギャップ検出の自動化

テストカバレッジは、テスト品質の「必要条件」ではあっても「十分条件」ではない。しかし、カバレッジの低い領域は確実にリスクが高い。Claude Codeにカバレッジレポートを読ませ、ギャップを自動検出させることで、テストの網羅性を効率的に向上させることができる。

# CLAUDE.md — カバレッジ分析設定

## カバレッジ要件
- 全体カバレッジ目標: 85%以上
- 新規コードカバレッジ: 90%以上
- ブランチカバレッジ: 80%以上

## カバレッジコマンド
- Vitest: `npx vitest --coverage --reporter=json --outputFile=coverage/report.json`
- Jest: `npx jest --coverage --coverageReporters=json-summary`

## カバレッジギャップ検出ワークフロー
1. テスト実装後、カバレッジレポートを生成
2. coverage/report.json を読み取り、カバレッジ80%未満のファイルを特定
3. 未カバーの行・ブランチに対して追加テストを作成
4. 再度カバレッジを計測し、目標達成を確認

Vitest MCP Serverを使用している場合、カバレッジの確認はさらにシームレスになる。MCPのツール呼び出しでテスト実行とカバレッジ取得を一括で行い、結果をAIが直接解析する。ターミナル出力の解析という不安定なプロセスを経由しないため、精度が高い。

カバレッジギャップの検出では、単純な行カバレッジだけでなく、ブランチカバレッジに注目すべきだ。条件分岐の片側しかテストされていないコードは、行カバレッジでは100%に見えても実際にはリスクがある。CLAUDE.mdにブランチカバレッジの閾値を明記し、Claude Codeに条件分岐の全パスをカバーするテストを要求することが重要だ。

テスト品質ピラミッドE2E テストPlaywright遅い・高コスト・高信頼10%統合テストAPI / Service中速・中コスト・中信頼20%ユニットテストJest / Vitest速い・低コスト・基盤70%速度信頼度推奨比率70% / 20% / 10%
項目 Vitest Jest
速度 高速(Vite/esbuild) 中速
ESM対応 ネイティブ 設定要
TypeScript 設定不要 ts-jest/babel要
設定量 最小 中程度
エコシステム 成長中 成熟
Claude Code連携 MCP Server有 CLAUDE.md設定
推奨プロジェクト 新規/Viteベース 既存/CRA/Next.js

結論として、新規プロジェクトであればVitestを強く推奨する。MCP Serverによるネイティブ統合、ESMのネイティブサポート、TypeScriptの設定レス対応——これらはすべてAI×TDDワークフローの摩擦を最小化する。一方、既存のJestプロジェクトを無理にVitestへ移行する必要はない。CLAUDE.mdでJest固有のパターンを明記し、Hooksでテスト実行を自動化すれば、十分に高品質なTDDワークフローを構築できる。

Playwright E2Eテスト自動生成——MCPとSkillで構築する統合テスト

ユニットテストがコードの「部品」を検証するのに対し、E2E(End-to-End)テストはアプリケーション全体の「振る舞い」を検証する。ユーザーがブラウザで行う操作——ページへのアクセス、フォーム入力、ボタンクリック、結果の確認——をそのまま自動化するのがE2Eテストの役割だ。本章では、Playwrightを使ったE2Eテストの自動生成を、Claude Codeとの統合の観点から解説する。

Playwright MCP Serverの導入手順

PlaywrightもVitest同様にMCP Serverを提供している。これにより、Claude Codeがブラウザの操作、スクリーンショットの取得、DOMの検査を直接行えるようになる。

# Playwrightのインストール
npm install -D @playwright/test
npx playwright install

# MCP Serverパッケージのインストール
npm install -D @anthropic/playwright-mcp

.mcp.jsonにPlaywright MCP Serverの設定を追加する。

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@anthropic/playwright-mcp", "--headless"],
      "env": {
        "BASE_URL": "http://localhost:3000"
      }
    }
  }
}

Playwright MCP Serverが提供する主要な機能は以下の通りだ。

  • ブラウザ操作——ページ遷移、クリック、入力、スクロールなどのブラウザ操作をプログラマティックに実行
  • スクリーンショット——現在のページ状態をスクリーンショットとして取得し、AIが視覚的に確認
  • DOM検査——特定の要素のテキスト、属性、表示状態を取得
  • ネットワーク監視——APIリクエスト/レスポンスの傍受と検証

MCP Serverを使うことで、Claude Codeは「実際にブラウザを操作して動作確認しながらテストを書く」ことが可能になる。これは従来のテスト作成とは根本的に異なるアプローチだ。AIが推測でセレクタを書くのではなく、実際のDOMを確認した上で最適なセレクタを選択できるため、テストの安定性が大幅に向上する。

Playwright Agents(Planner/Generator/Healer)の活用

Playwrightのエコシステムには、テストのライフサイクルを管理する3種類の「エージェント」パターンが存在する。これらはClaude Codeのマルチエージェント構成と組み合わせることで、強力なE2Eテスト自動化パイプラインを構築できる。

Agent 役割 入力 出力
Planner テストシナリオ設計 ユーザーストーリー, 要件 テスト計画, ステップ定義
Generator テストコード生成 テスト計画, POMクラス Playwrightテストコード
Healer テスト修復 失敗テスト, DOMスナップショット 修正されたセレクタ/テスト

Planner Agentは、ユーザーストーリーや要件定義を入力として受け取り、テストシナリオを設計する。「ユーザーがログインフォームにメールアドレスとパスワードを入力し、ログインボタンをクリックすると、ダッシュボードに遷移する」といった自然言語の要件を、テストステップの構造化されたリストに変換する。Claude Codeのサブエージェントとして動作させ、要件ドキュメントを読み取ってテスト計画を出力させることが効果的だ。

Generator Agentは、テスト計画をもとに実際のPlaywrightテストコードを生成する。Page Object Model(POM)クラスが既に存在する場合はそれを利用し、存在しない場合は新規にPOMクラスも同時に生成する。このAgentはMCP Serverと連携し、実際のページを確認しながらセレクタを決定する。

Healer Agentは、UIの変更によって壊れたテストを自動修復する役割を担う。テストが失敗すると、失敗時のDOMスナップショットとスクリーンショットを取得し、壊れたセレクタを特定して修正する。フロントエンドの頻繁なUI変更がE2Eテストのメンテナンスコストを押し上げる——この課題に対する直接的な解決策だ。

これら3つのAgentをClaude Codeのワークフローに統合する場合、CLAUDE.mdに以下のようなルールを設定する。

# E2Eテスト ワークフロー

## テストシナリオ設計
- 新しいユーザーストーリーには必ずE2Eテスト計画を作成
- テスト計画は `e2e/plans/` ディレクトリに Markdown で保存
- 正常系 + エラー系 + エッジケースの3パターンを最低限カバー

## テストコード生成
- Page Object Model パターンを必須とする
- POMクラスは `e2e/pages/` ディレクトリに配置
- セレクタはアクセシビリティベース(getByRole, getByLabel)を優先
- data-testid は最終手段としてのみ使用

## テスト修復
- E2Eテストが失敗した場合、まずセレクタの変更を確認
- DOMスナップショットと比較して最小限の修正を適用
- 修復後は関連する全テストを再実行して副作用がないことを確認

Page Object Modelによるテスト構造設計

Page Object Model(POM)は、E2Eテストの保守性を劇的に向上させるデザインパターンだ。ページのUI要素と操作をクラスにカプセル化し、テストコードからページの実装詳細を隠蔽する。セレクタの変更が発生した場合、POMクラスの1箇所を修正するだけで、そのページに関するすべてのテストが修正される。

// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test'

export class LoginPage {
  readonly emailInput: Locator
  readonly passwordInput: Locator
  readonly submitButton: Locator
  readonly errorMessage: Locator

  constructor(private page: Page) {
    this.emailInput = page.getByLabel('メールアドレス')
    this.passwordInput = page.getByLabel('パスワード')
    this.submitButton = page.getByRole('button', { name: 'ログイン' })
    this.errorMessage = page.getByRole('alert')
  }

  async goto() {
    await this.page.goto('/login')
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email)
    await this.passwordInput.fill(password)
    await this.submitButton.click()
  }
}

// tests/login.spec.ts
import { test, expect } from '@playwright/test'
import { LoginPage } from '../pages/LoginPage'

test.describe('ログイン機能', () => {
  test('正しい認証情報でログインできること', async ({ page }) => {
    // Arrange
    const loginPage = new LoginPage(page)
    await loginPage.goto()

    // Act
    await loginPage.login('user@example.com', 'password123')

    // Assert
    await expect(page).toHaveURL('/dashboard')
  })

  test('誤ったパスワードでエラーが表示されること', async ({ page }) => {
    // Arrange
    const loginPage = new LoginPage(page)
    await loginPage.goto()

    // Act
    await loginPage.login('user@example.com', 'wrong-password')

    // Assert
    await expect(loginPage.errorMessage).toBeVisible()
    await expect(loginPage.errorMessage).toContainText('認証に失敗しました')
  })
})

POMパターンをClaude Codeに徹底させるためのポイントは3つある。

第一に、セレクタ戦略の優先順位を明確にする。Playwrightの推奨するアクセシビリティベースのセレクタ(getByRolegetByLabelgetByText)を最優先とし、data-testidはアクセシビリティセレクタでは一意に特定できない場合の代替手段とする。CSSセレクタや XPath は原則として禁止する。この優先順位をCLAUDE.mdに明記すれば、Claude Codeは一貫して適切なセレクタを選択する。

第二に、POMクラスの粒度を統一する。1ページ1クラスを基本とし、ページ内の複雑なコンポーネント(モーダルダイアログ、サイドバーなど)は個別のクラスに分離する。POMクラスは「操作」のみを定義し、「アサーション」はテストコード側に記述するという原則を守る。

第三に、待機戦略を標準化する。Playwrightの自動待機機能を活用し、明示的なwaitForTimeout()の使用を禁止する。固定時間の待機はFlakyテストの最大の原因であり、PlaywrightのwaitForSelector()waitForURL()、あるいはアサーションの自動リトライ機能で代替すべきだ。

CI/CDパイプラインとの統合設定

E2Eテストは本番に最も近い環境で実行してこそ意味がある。GitHub Actionsを使ったCI/CDパイプラインへの統合は、Playwright E2Eテストの価値を最大化する重要なステップだ。

name: E2E Tests
on:
  pull_request:
    branches: [main, develop]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

CI環境でのE2Eテスト実行では、いくつかの追加考慮事項がある。

ブラウザのインストール——npx playwright install --with-depsで、テスト実行に必要なブラウザバイナリとシステム依存パッケージをインストールする。--with-depsフラグはLinux環境で必要なシステムライブラリ(libgbm、libasound2など)も自動的にインストールするため、CI環境では必須だ。

アーティファクトの収集——テスト失敗時にスクリーンショット、動画、トレースログを自動保存する。これらのアーティファクトは、失敗原因の特定に不可欠だ。if: failure()条件を付けることで、失敗時のみアーティファクトをアップロードし、ストレージコストを最小化できる。

並列実行の設定——Playwrightはデフォルトでテストを並列実行する。CI環境ではワーカー数をCPUコア数に合わせて調整する。playwright.config.tsworkers: process.env.CI ? 2 : undefinedのように設定し、CI環境では並列度を制限してリソース競合を防ぐのが定石だ。

Claude Codeが生成するE2EテストがCIで安定的に動作するよう、CLAUDE.mdに以下を追記しておくとよい。

# E2Eテスト CI対応ルール
- テストはヘッドレスモードで動作すること
- 固定のテストデータを使用し、外部サービスに依存しないこと
- テストの実行順序に依存しないこと(各テストが独立)
- タイムアウトは30秒以内に収まるよう設計すること

Flakyテスト撲滅戦略——AIが生むテスト不安定性を根本解決する

AIが生成するテストには、人間が書くテストとは異なる特有の「Flaky(不安定)パターン」がある。Claude Codeは大量のテストを高速に生成できるが、その分だけFlakyテストも量産されるリスクがある。一見パスするが、実行環境やタイミングによって結果が変わるテスト——これが蓄積すると、テストスイート全体の信頼性が崩壊する。「テストが落ちた?ああ、あれはFlakyだから無視して」——この一言がテスト文化を殺す。本章では、AIが生むFlakyテストの根本原因を分類し、それぞれの解決策を体系的に示す。

Flakyテストの3大原因

AIが生成するFlakyテストの原因は、大きく3つに分類できる。

1. 時間依存——Date.now()setTimeoutsetIntervalに依存するテスト。AIは「現在の時刻」を基準にしたテストを安易に書きがちだ。テスト作成時と実行時で時刻が異なるため、日付境界やタイムゾーンの問題で不安定になる。

2. 外部サービス依存——APIコール、データベースクエリ、ネットワークアクセスに依存するテスト。AIは実装コードをそのままテストに持ち込む傾向があり、外部サービスの応答遅延やダウンタイムでテストが不安定になる。

3. 実行順序依存——テスト間で共有状態(グローバル変数、シングルトン、データベースレコード)を持つテスト。AIは個別のテストケースに集中するため、テスト間の相互影響を見落としやすい。テストAが生成したデータがテストBの前提条件を壊す——この種の問題は、テストを個別に実行すると再現しないため、発見が困難だ。

CLAUDE.mdに以下のルールを設定することで、これらのFlakyパターンをAIに事前に回避させることができる。

# Flakyテスト防止ルール(必須)

## 時間依存の排除
- Date.now() を直接使用するテスト禁止
- vi.useFakeTimers() または jest.useFakeTimers() で時間を制御
- タイムゾーンに依存する場合は UTC に固定

## 外部サービス依存の排除
- テスト内で実際のHTTPリクエストを発行禁止
- 外部APIは MSW (Mock Service Worker) でモック
- データベースはテスト専用DB + トランザクションロールバック

## 実行順序依存の排除
- グローバル変数の変更禁止
- 各テストは beforeEach で状態を初期化
- テストIDにはランダムサフィックスを付与(衝突防止)

Mock戦略のベストプラクティス

Flakyテストの最も効果的な防止策は、適切なモック戦略だ。しかし、「何でもモックすればよい」というわけではない。過剰なモックはテストの意味を失わせ、実装への過度な結合を生む。以下に、状況別のモック戦略を示す。

時間依存のモック——vi.useFakeTimers()(Vitest)またはjest.useFakeTimers()(Jest)を使用する。テスト内で時間を完全に制御し、advanceTimersByTime()で必要な時間を進める。テスト終了後は必ずvi.useRealTimers()でリアルタイマーに戻す。

HTTP通信のモック——MSW(Mock Service Worker)を強く推奨する。MSWはネットワークレベルでリクエストをインターセプトするため、テスト対象コードの実装に依存しない。fetchaxiosといったHTTPクライアントのモックは、クライアント変更時にテストが壊れるため避けるべきだ。

データベースのモック——2つのアプローチがある。ユニットテストでは、リポジトリパターンを使ってデータアクセス層をインターフェースで抽象化し、インメモリ実装に差し替える。統合テストでは、テスト専用データベースをトランザクション内で操作し、テスト終了時にロールバックする。

// 時間依存のFlaky修正例

// Flaky: 時間依存
it('should expire after 30 minutes', () => {
  const token = createToken()
  // 30分後をシミュレート... しかし実行環境の速度に依存
  setTimeout(() => {
    expect(token.isExpired()).toBe(true)
  }, 1800000)
})

// Fixed: 時間をモック
it('should expire after 30 minutes', () => {
  vi.useFakeTimers()
  const token = createToken()

  vi.advanceTimersByTime(30 * 60 * 1000)

  expect(token.isExpired()).toBe(true)
  vi.useRealTimers()
})

// Flaky: 外部API依存
it('should fetch user data', async () => {
  const user = await fetchUser(1)  // 実際のAPIを呼ぶ
  expect(user.name).toBe('Taro')
})

// Fixed: MSWでモック
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
  http.get('/api/users/1', () => {
    return HttpResponse.json({ id: 1, name: 'Taro' })
  })
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

it('should fetch user data', async () => {
  const user = await fetchUser(1)
  expect(user.name).toBe('Taro')
})

// Flaky: 共有状態
let counter = 0
it('should increment counter', () => {
  counter++
  expect(counter).toBe(1)  // 他のテストが先に実行されると失敗
})

// Fixed: テストごとに独立した状態
it('should increment counter', () => {
  const counter = createCounter()  // 各テストで新規作成
  counter.increment()
  expect(counter.value).toBe(1)
})

テストの決定性を保証するパターン集

Flakyテストの排除は、個別のモック適用だけでは不十分だ。テストスイート全体の「決定性」——同じ入力に対して常に同じ結果を返す性質——を保証するためのパターンを、設計レベルで導入する必要がある。

ファクトリ関数パターン——テストデータの生成にファクトリ関数を使用する。各テストが独自のデータセットを生成し、他のテストとデータを共有しない。

// テストデータファクトリ
function createUser(overrides?: Partial<User>): User {
  return {
    id: `user_${crypto.randomUUID()}`,
    name: 'Test User',
    email: `test-${Date.now()}@example.com`,
    createdAt: new Date('2025-01-01T00:00:00Z'),  // 固定日付
    ...overrides
  }
}

// 使用例
it('should update user name', async () => {
  const user = createUser({ name: 'Original' })
  const updated = await updateUser(user.id, { name: 'Updated' })
  expect(updated.name).toBe('Updated')
})

ユニークID生成パターン——テストごとにユニークな識別子を使い、テスト間のデータ衝突を防止する。crypto.randomUUID()やタイムスタンプベースのIDを活用する。

セットアップ/ティアダウンの徹底——beforeEachでテスト環境を初期化し、afterEachでクリーンアップする。特にシングルトンやグローバルキャッシュは、テスト間のリークの温床だ。明示的にリセットすることで、テストの実行順序への依存を排除する。

CI環境でのリトライ戦略と根本対策の切り分け

CIパイプラインでのテストリトライは「必要悪」だ。E2Eテストでは、ブラウザの起動タイミングやネットワークレイテンシなど、制御困難な要因が存在する。そのため、E2Eテストに対する1-2回のリトライは現実的な妥協として許容される。

しかし、リトライは決して根本的な解決策ではない。リトライ設定を「付けておしまい」にすると、Flakyテストが静かに蓄積し、CIの実行時間が無駄に伸び、最終的にはテストスイート全体の信頼性が崩壊する。

重要なのは、リトライで成功したテストを「Flaky候補」として記録することだ。Playwrightではplaywright.config.tsのreporterでretriesとともにレポートを出力し、リトライで成功したテストのリストを追跡できる。週次でこのリストをレビューし、根本原因を修正するワークフローを確立する。

// playwright.config.ts(CI環境でのリトライ設定)
import { defineConfig } from '@playwright/test'

export default defineConfig({
  retries: process.env.CI ? 2 : 0,  // CIのみリトライ許可
  reporter: [
    ['html'],
    ['json', { outputFile: 'test-results/results.json' }],
    // リトライで成功したテストを追跡するカスタムレポーター
    ['./reporters/flaky-tracker.ts']
  ],
  use: {
    trace: 'on-first-retry',  // リトライ時のみトレースを取得
    screenshot: 'only-on-failure'
  }
})

Flakyテストの根本対策として最も効果的なのは、CLAUDE.mdに防止ルールを設定し、AIが最初からFlakyでないテストを生成することだ。事後の修正より事前の予防——これはソフトウェア品質一般に通じる原則であり、AI×TDDにおいてはなおさら重要だ。

従来TDD vs AI×TDD 完全比較——速度・品質・コストの定量評価

ここまで、AI×TDDの具体的な実装方法を解説してきた。では、従来のTDD(人間が全て手動で行うTDD)と比較して、AI×TDDはどの程度の改善をもたらすのか。本章では、開発速度、テスト品質、コストの3軸で定量的な比較を行い、AI×TDD導入の判断材料を提供する。

開発速度の比較

従来のTDDは「最初は遅く、後で速い」という特性を持つ。テスト作成のオーバーヘッドにより初期の開発速度は低下するが、バグの早期発見とリファクタリングの安全性により、プロジェクト後半の開発速度が向上する。この「投資と回収」のサイクルが、TDD導入をためらわせる最大の要因だった。

AI×TDDは、この構造を根本的に変える。テスト作成自体がAIにより3-5倍高速化されるため、初期のオーバーヘッドが大幅に縮小する。具体的な速度改善を3つのメトリクスで見てみよう。

Time-to-First-Test(最初のテスト完成までの時間)——従来TDDでは、テスト対象の仕様を理解し、テストフレームワークのセットアップを確認し、テストケースを設計・記述するまでに15-30分かかることが一般的だ。AI×TDDでは、CLAUDE.mdのルールに基づきClaude Codeが即座にテストを生成するため、3-8分に短縮される。

Cycle Time(Red-Green-Refactorの1サイクル完了時間)——従来TDDでは1サイクルに10-20分。AI×TDDの基本構成では5-10分、フル構成(マルチエージェント)では3-5分に短縮される。特にRefactorフェーズでの改善が著しい。AIが複数のリファクタリング案を提示し、テストで安全性を確認しながら適用するプロセスが高速だ。

Time-to-Feature(機能完成までの総時間)——テスト込みの機能開発全体で見ると、従来TDDはテストなし開発の0.8-1.2倍(テスト作成分のオーバーヘッドと、バグ修正時間の削減が相殺)。AI×TDDの基本構成では0.5-0.7倍、フル構成では0.3-0.5倍にまで短縮される。テスト作成のコスト削減に加え、AIによる実装の高速化が加わるためだ。

テストカバレッジの品質差

カバレッジの「量」と「質」は区別する必要がある。行カバレッジ90%であっても、重要なエッジケースがテストされていなければ品質は低い。ここでは量と質の両面で比較する。

行カバレッジ——従来TDDでは開発者の規律に大きく依存し、プロジェクト全体で60-80%が典型的だ。AI×TDDでは、CLAUDE.mdにカバレッジ閾値を設定することで、一貫して80-95%を達成できる。特にフル構成(レビューエージェントがカバレッジを検証)では90%以上が安定的に維持される。

ブランチカバレッジ——条件分岐の全パスをカバーしているかを測るブランチカバレッジは、行カバレッジより重要だが、従来TDDでは見落としが多い。AI×TDDでは、Claude Codeが条件分岐を網羅的に分析し、各ブランチに対するテストケースを自動生成するため、ブランチカバレッジも向上する。

ミューテーションテストスコア——テストの「本当の品質」を測る最も信頼性の高い指標がミューテーションスコアだ。プロダクションコードに意図的な変異(条件の反転、定数の変更など)を加え、テストがその変異を検出できるかを測定する。従来TDDでは70-85%、AI×TDD基本構成では65-80%(AIが生成するテストは表面的なアサーションに偏る傾向がある)、フル構成では80-90%(レビューエージェントがアサーションの品質を検証するため改善される)。

注目すべきは、AI×TDDの基本構成におけるミューテーションスコアが従来TDDより低い場合がある点だ。AIは「テストが通ること」を優先し、「テストが失敗すべき時に失敗すること」の検証が不十分になる傾向がある。これは、CLAUDE.mdでアサーションの具体性を要求するルールや、マルチエージェント構成でのレビュー強化によって対策できる。

コスト分析——トークン消費とROI

AI×TDDの導入コストで最も目に見えるのは、トークン消費の増加だ。テスト作成に追加のトークンが必要になるため、機能あたりのトークンコストは確実に増加する。しかし、プロジェクトライフサイクル全体で見ると、バグ修正やデバッグに費やすトークンが大幅に削減され、ROIはプラスになることが多い。

具体的な数値で見てみよう。中規模の機能(APIエンドポイント1つ + フロントエンド画面1つ)を実装する場合を想定する。

テストなし開発——実装: 10K tokens + デバッグ/バグ修正: 15K tokens = 合計25K tokens。テストがないため、バグは手動テストやユーザー報告で発見される。修正にはコードの理解、再現手順の確認、修正、動作確認が必要で、多くのトークンを消費する。

AI×TDD基本構成——テスト作成: 8K tokens + 実装: 8K tokens + リファクタリング: 4K tokens + バグ修正: 3K tokens = 合計23K tokens。テスト作成にトークンを使うが、テストが実装を導くため実装自体が効率化され、バグ修正のトークンも激減する。

AI×TDDフル構成——テスト作成: 12K tokens + 実装: 6K tokens + レビュー: 5K tokens + リファクタリング: 3K tokens + バグ修正: 1K tokens = 合計27K tokens。トークン総量は最大だが、バグ修正コストは最小。長期メンテナンスコストの削減を加味すると最もROIが高い。

ブレークイーブンポイント(損益分岐点)は、プロジェクト開始から約2-3週間だ。初期のテスト作成とルール設定に追加コストがかかるが、バグの早期発見と修正コスト削減により、3週間目以降はTDDあり開発の方がトータルコストが低くなる傾向にある。

どの規模のプロジェクトでAI×TDDが有効か

AI×TDDの効果はプロジェクト規模によって異なる。以下に規模別の推奨構成を示す。

小規模プロジェクト(1,000行未満)——スクリプトやユーティリティツールなど。AI×TDDは任意。CLAUDE.mdに基本的なテストルールを記述するだけで十分。テストの投資対効果は低いが、TDDの練習としては有効。

中規模プロジェクト(1,000-50,000行)——Webアプリケーション、APIサービスなど。AI×TDDを推奨。Skills + Hooksによる半自動TDDが効果的。テストカバレッジの維持とリファクタリングの安全性が、プロジェクトの持続的な成長を支える。

大規模プロジェクト(50,000行超)——エンタープライズアプリケーション、プラットフォームなど。AI×TDDは必須。マルチエージェント構成によるフル自動TDDが最大の効果を発揮する。コードベースが大きくなるほど、テストなしの変更のリスクは指数的に増大する。

レガシープロジェクト(テストなし)——最も難易度が高いが、効果も大きい。まず「特性テスト(characterization tests)」から始める。特性テストとは、既存コードの現在の振る舞いをそのまま記録するテストだ。コードの意図ではなく、実際の動作をテストする。これにより、リファクタリングの安全な基盤を構築し、段階的にTDDを導入できる。

評価軸 従来TDD AI×TDD(基本) AI×TDD(フル構成)
初期セットアップ時間 なし 30分 2-4時間
テスト作成速度 1x(基準) 3-5x 5-8x
実装速度 0.8x(テスト分遅い) 2-3x 3-5x
テストカバレッジ 60-80% 80-90% 90-95%
ミューテーションスコア 70-85% 65-80% 80-90%
バグ混入率 中-低
トークンコスト/機能 +40-60% +80-120%
バグ修正コスト削減 -50% -60% -75%
推奨プロジェクト規模 全規模 中-大 大規模
学習曲線 中-高

この比較表が示す通り、AI×TDDは「従来TDDの上位互換」ではなく、「従来TDDをAIで加速・強化するアプローチ」だ。TDDの原則——テストファースト、Red-Green-Refactor、小さなステップ——はそのままに、AIがテスト作成と実装の速度を引き上げる。重要なのは、AIを使うからといってTDDの規律を緩めてはならないということだ。むしろ、AIの速度を活かすために、より厳密な規律が必要になる。

段階的導入ロードマップ——1日で始め4週間で完成させる3レベル戦略

AI×TDDを導入する際に最も重要なのは、「完璧を目指さず、段階的に進める」ことだ。初日からマルチエージェント構成を組もうとすると、設定の複雑さに圧倒されて挫折する。逆に、CLAUDE.mdにTDDルールを1行追加するだけなら、今すぐ始められる。本章では、3つのレベルに分けた段階的導入ロードマップを提示する。各レベルは前のレベルの上に構築され、段階的にAI×TDDの成熟度を高める。

段階的導入ロードマップDay 1Week 1Week 2-4CLAUDE.md にTDD ルール追加テストファースト原則命名規則禁止事項難易度効果TDDの意識付けSkills + Hooks自動強制SKILL.md 設定tdd-guard 導入自動テスト実行難易度効果TDDを自動強制マルチエージェントTDD3エージェント分離CI/CD 統合カバレッジ自動化難易度効果完全自動TDD複雑さ・効果ともに段階的に向上

Level 1(Day 1)——CLAUDE.mdにTDDセクション追加

最初のステップは、最も簡単で最も効果の高いアクションだ。プロジェクトのCLAUDE.mdファイルにTDDセクションを追加する。所要時間は30分。これだけで、Claude Codeのテスト生成品質が即座に向上する。

具体的な手順は以下の通りだ。

ステップ1: テストコマンドの定義(5分)

# CLAUDE.md — TDDセクション(Level 1)

## テスト
- テストランナー: vitest(または jest)
- テスト実行: `npx vitest run`
- 単一テスト: `npx vitest run path/to/test`
- カバレッジ: `npx vitest --coverage`

ステップ2: TDDワークフローの定義(10分)

## TDDワークフロー(必須)
1. コード変更前に必ずテストを書く(テストファースト)
2. テストが失敗することを確認(Red)
3. テストを通す最小限のコードを実装(Green)
4. テストが通ることを確認後、リファクタリング(Refactor)
5. 全テストがパスすることを確認

ステップ3: テスト規約の定義(15分)

## テスト規約
- ファイル配置: `src/__tests__/` ディレクトリ
- 命名: `[対象].test.ts`
- 構造: describe → it(Arrange-Act-Assert)
- カバレッジ目標: 80%以上
- 外部依存は必ずモック

Level 1だけでも、以下の改善が期待できる。

  • Claude Codeが一貫してテストファーストで作業するようになる
  • テストファイルの配置と命名が統一される
  • テストの構造(Arrange-Act-Assert)が標準化される
  • カバレッジが60-70%に到達する(ルールなしの場合の30-50%から向上)

Level 2(Week 1)——Skills + Hooks自動強制

Level 1で基本的なTDDワークフローが定着したら、次はSkillsとHooksで自動強制を導入する。人間もAIも、ルールの「自主的な遵守」には限界がある。物理的な強制ゲートを設置することで、TDDの規律を確実に維持する。

ステップ1: Hookの設置(30分)

コード変更前にテストの存在を確認するHookを設定する。

// .claude/hooks/pre-edit.sh
#!/bin/bash

# 変更対象ファイルに対応するテストファイルの存在チェック
FILE="$1"

# テストファイル自体の変更はスキップ
if [[ "$FILE" == *.test.* ]] || [[ "$FILE" == *.spec.* ]]; then
  exit 0
fi

# srcディレクトリ内のファイルのみチェック
if [[ "$FILE" == src/* ]]; then
  TEST_FILE="${FILE%.ts}.test.ts"
  TEST_FILE="${TEST_FILE/src\//src/__tests__/}"

  if [ ! -f "$TEST_FILE" ]; then
    echo "ERROR: テストファイルが見つかりません: $TEST_FILE"
    echo "テストファースト: まずテストを作成してください"
    exit 1
  fi
fi

ステップ2: SKILL.mdの作成(1時間)

TDDワークフロー全体を定義するSKILL.mdを作成する。これにより、Claude Codeが/tddコマンドでTDDモードに入れるようになる。

# .claude/skills/tdd/SKILL.md

## TDD開発スキル

### 起動条件
このスキルは新機能の実装時に自動的に有効化されます。

### ワークフロー
1. 要件を分析し、テスト計画を作成
2. テストファイルを作成(Red)
3. テストを実行して失敗を確認
4. 最小限の実装コードを作成(Green)
5. テストを実行して成功を確認
6. リファクタリングを実施(Refactor)
7. 全テストを実行して回帰がないことを確認
8. カバレッジを確認し、80%未満なら追加テスト作成

### 制約
- 実装コードの前にテストが存在すること
- テストなしのプロダクションコード変更は禁止
- 各テストケースは1つの振る舞いのみ検証

ステップ3: tdd-guardの導入(30分)

tdd-guardは、Claude CodeのTDDワークフローを物理的に強制するオープンソースツールだ。テストファイルが存在しないプロダクションコードの変更をブロックし、テストファースト原則を自動的に適用する。

# tdd-guardのインストール
npm install -D tdd-guard

# 設定ファイルの作成
# tdd-guard.config.json
{
  "sourceDir": "src",
  "testDir": "src/__tests__",
  "testSuffix": ".test.ts",
  "excludePatterns": ["*.d.ts", "index.ts", "types.ts"],
  "enforceTestFirst": true
}

Level 2の導入により、以下の改善が期待できる。

  • TDDワークフローが物理的に強制される(回避不可能)
  • テストカバレッジが80-85%に安定的に到達
  • テストの品質が標準化される(SKILL.mdのガイドラインに従う)
  • テスト作成速度が3倍に向上(AIがパターンを学習)

Level 3(Week 2-4)——マルチエージェントTDD構築

最上位のLevel 3では、マルチエージェント構成による全自動TDDパイプラインを構築する。テスト設計、実装、レビューの各フェーズを独立したエージェントが担当し、相互牽制によってテスト品質を最大化する。

ステップ1: エージェント構成の設計(2時間)

3エージェント構成——テストエージェント、実装エージェント、レビューエージェント——を設計する。各エージェントの責任範囲、入出力インターフェース、コンテキスト分離のルールを定義する。

ステップ2: サブエージェント定義の作成(3時間)

各エージェントのプロンプト、許可ツール、出力フォーマットを定義する。特に重要なのは、テストエージェントが実装コードを参照できず、実装エージェントがテストコードを変更できないという制約だ。

ステップ3: CI/CD統合(2時間)

GitHub ActionsまたはCI環境で、マルチエージェントTDDパイプラインを自動実行する設定を行う。プルリクエスト作成時にテストの自動生成・実行・レビューが行われる環境を構築する。

Level 3の導入により、以下の改善が期待できる。

  • テストカバレッジが90-95%に安定的に到達
  • テストの「正直さ」が物理的に保証される(コンテキスト分離)
  • 開発速度が5倍に向上(並列実行)
  • バグ混入率が75%以上削減

導入チェックリストとマイルストーン

各レベルの導入完了を判断するためのチェックリストを以下に示す。

Level 1 完了チェック

  • CLAUDE.mdにTDDセクションが存在する
  • テストコマンドが正常に動作する
  • Claude Codeがテストファーストで作業している(直近5回の実装で確認)
  • テストカバレッジが60%以上

Level 2 完了チェック

  • Hookが正常に動作し、テストなし変更をブロックしている
  • SKILL.mdが存在し、TDDワークフローが自動適用されている
  • テストカバレッジが80%以上で安定している
  • 直近2週間でFlakyテストが発生していない

Level 3 完了チェック

  • 3エージェント構成が正常に動作している
  • エージェント間のコンテキスト分離が機能している
  • テストカバレッジが90%以上で安定している
  • CI/CDパイプラインでマルチエージェントTDDが自動実行されている
  • ミューテーションスコアが80%以上
Level 1 Level 2 Level 3
期間 Day 1 Week 1 Week 2-4
難易度
必要時間 30分 2-3時間 1-2日
設定対象 CLAUDE.md Skills + Hooks マルチエージェント
期待カバレッジ 60-70% 80-85% 90-95%
自動化レベル ルールベース 半自動 全自動
推奨チーム 個人 小規模チーム 中-大規模チーム

段階的導入の最大の利点は、各レベルで効果を実感しながら進められることだ。Level 1の30分の投資で即座にテスト品質が向上するのを体験すれば、Level 2への投資へのモチベーションが自然と生まれる。「完璧な設定を最初から」ではなく、「今日できる最小のアクションから」——これがAI×TDD導入成功の鍵だ。

よくある質問(FAQ)

AI×TDDの導入を検討する際に寄せられる頻度の高い質問と、その回答をまとめた。

Q: TDD未経験でもClaude CodeでTDDを始められますか?

A: はい、Claude Codeは最適な入門ツールです。CLAUDE.mdにTDDルールを設定するだけで、AIがRed-Green-Refactorサイクルを自動的に実行します。Level 1の設定は30分で完了し、TDDの基本を体験しながら学べます。従来のTDD学習では「テストの書き方」と「TDDのリズム」を同時に習得する必要がありましたが、Claude Codeがテスト作成を支援するため、TDDの考え方に集中できます。ただし、AIに完全に依存するのではなく、生成されたテストを読み、なぜそのテストが必要なのかを理解する習慣をつけることが重要です。TDDの原則を理解していれば、AIが生成するテストの品質を適切に判断でき、CLAUDE.mdのルールも効果的に設計できるようになります。

Q: TDDを導入すると開発速度が落ちませんか?

A: 短期的にはテスト作成分のオーバーヘッドがありますが、AI×TDDでは従来のTDDと異なり、テスト作成自体が3-5倍高速化されます。中長期的にはバグ修正時間が60-75%削減され、リファクタリングも安全に行えるため、トータルの開発速度は向上します。本記事のH2-10で詳述した通り、機能あたりの開発時間は従来のテストなし開発と比較して、AI×TDD基本構成で0.5-0.7倍に短縮されます。特に重要なのは、テストがあることで「変更の恐怖」がなくなり、積極的なリファクタリングが可能になることです。これが中長期的なコードベースの健全性と開発速度の維持につながります。

Q: トークンコストはどのくらい増えますか?

A: 機能あたり40-120%のトークン増加が見込まれます。ただし、バグ修正やデバッグにかかるトークンが大幅に減少するため、プロジェクト全体ではROIがプラスになることが多いです。特に中-大規模プロジェクトでは、バグの早期発見による工数削減効果が大きくなります。具体的には、Level 1(CLAUDE.mdのみ)で+40%、Level 3(マルチエージェント構成)で+120%程度のトークン増加ですが、バグ修正トークンの削減により、ブレークイーブンポイントは約2-3週間です。プロジェクト開始から1ヶ月以降は、TDDを導入した方がトータルのトークンコストが低くなる傾向にあります。

Q: 既存プロジェクト(テストなし)にTDDを導入できますか?

A: できます。まず「特性テスト(characterization tests)」から始めてください。既存コードの現在の振る舞いをそのままテストとして記録し、安全なリファクタリングの基盤を作ります。新規機能追加時からTDDサイクルを適用し、段階的にカバレッジを拡大していくのが現実的なアプローチです。Claude Codeに「このファイルの現在の振る舞いを特性テストとして記録して」と指示すれば、既存コードの動作を網羅的にテスト化してくれます。一度に全てをテスト化しようとせず、変更の多いモジュールから優先的にテストを追加していくことを推奨します。

Q: VitestとJest、どちらを選ぶべきですか?

A: 新規プロジェクトならVitestを推奨します。ESMネイティブ対応、TypeScript設定不要、高速な実行速度、そしてMCP Server連携が利用可能です。既存のJestプロジェクトは無理に移行する必要はありません。CLAUDE.mdでJest固有の設定を明記すれば、Claude Codeは適切にJestパターンでテストを生成します。H2-7で解説した比較表の通り、VitestはMCP Serverによるネイティブ統合が最大の強みで、Claude Codeとの親和性が高いです。ただし、Jestの成熟したエコシステム(豊富なプラグイン、ドキュメント、コミュニティサポート)も大きな価値があり、既存プロジェクトでの安定性を考慮するとJest継続も合理的な選択です。

Q: MCP Serverとの併用は必須ですか?

A: 必須ではありません。CLAUDE.mdとHooksだけでも十分なTDDワークフローを構築できます。MCP Serverは「あると便利」なレベルで、テスト実行結果のリアルタイム統合やカバレッジの可視化を強化します。Level 2以降で段階的に導入するのが理想的です。MCP Serverの主な利点は、Claude Codeがテスト結果を構造化されたデータとして受け取れることで、ターミナル出力の解析より正確な判断が可能になります。しかし、Hooksでテストコマンドの実行と結果の表示を自動化すれば、MCP Serverなしでも実用的なTDDワークフローは十分に実現できます。

Q: Claude CodeがTDDルールを無視する場合はどうすればよいですか?

A: 3つの対策を段階的に適用してください。(1) CLAUDE.mdの記述をより具体的かつ明確にする(曖昧な表現を排除)。例えば「テストを書くこと」ではなく「プロダクションコードの変更前に、対応するテストファイルを作成し、テストが失敗することを確認すること」と具体的に記述します。(2) Hooksで物理的な強制ゲートを設置する(テストなしではコード変更をブロック)。これにより、AIがルールを「忘れる」ことが不可能になります。(3) マルチエージェント構成で権限を物理的に分離する。テストエージェントと実装エージェントを分離すれば、実装エージェントがテストをスキップすることは構造的にできなくなります。ほとんどの場合、(2)のHooks導入で解決します。

Q: フロントエンド(React/Vue)のTDDはどうすればよいですか?

A: コンポーネントのTDDにはReact Testing LibraryまたはVue Test Utilsを使用します。CLAUDE.mdにコンポーネントテストのパターンを明記してください。ポイントは「実装の詳細ではなく、ユーザーの振る舞いをテストする」ことです。getByRolegetByTextなどのアクセシビリティベースのセレクタを使い、DOMの内部構造に依存しないテストを書くよう指示します。具体的には、「ボタンをクリックしたらモーダルが表示される」「フォームに入力して送信したらAPIが呼ばれる」といったユーザー視点の振る舞いをテストします。コンポーネントの内部状態や内部メソッドを直接テストすることは避け、外部から観察可能な振る舞いに集中します。これにより、リファクタリング耐性の高いテストが書けます。

まとめ——テスト駆動がAI開発の「品質の床」を決める

本記事の要点(7つのキーポイント)

本記事で解説した内容を、7つのキーポイントに集約する。

1. LLMには「テスト後付けバイアス」がある——TDDで根本解決。LLMは実装を先に書き、テストを後から追加する傾向がある。テストが実装に合わせて書かれるため、本来検出すべきバグを見逃す。テストファーストの原則を強制することで、このバイアスを構造的に排除する。

2. Red-Green-Refactorは3フェーズの鉄則——AIにも例外なし。Red(失敗するテスト)、Green(最小実装)、Refactor(改善)の3フェーズは、TDDの核心であり、AIの速度を活かしつつも絶対に省略してはならない。特にRedフェーズ——テストが失敗することの確認——を省くと、テストが何も検証していない「死んだテスト」になるリスクがある。

3. CLAUDE.mdはTDDの「憲法」——明確なルールが品質を担保。テストフレームワーク、命名規則、カバレッジ閾値、モック戦略——これらをCLAUDE.mdに明文化することで、AIが一貫したテスト品質を維持する。曖昧な指示は曖昧なテストを生む。具体的で明確なルールだけが、安定した品質を保証する。

4. Skills + HooksでTDDを自動化——人間の意志力に頼らない。ルールは忘れられ、省略され、無視される。HooksによるTDDの物理的な強制と、Skillsによるワークフローの標準化が、この問題を根本から解決する。テストなしのコード変更を物理的にブロックすれば、TDDの規律は自動的に維持される。

5. マルチエージェント分離で「正直なテスト」を実現——コンテキスト汚染の排除。テストと実装を同じコンテキストで扱うと、AIはテストを実装に合わせて「最適化」してしまう。テストエージェント、実装エージェント、レビューエージェントを物理的に分離することで、テストの独立性と正直さを構造的に保証する。

6. Flakyテスト対策は設計の問題——DI/モック/決定性の3原則。Flakyテストの根本原因は、テストの書き方ではなくプロダクションコードの設計にある。Dependency Injectionで外部依存を明示化し、適切なモック戦略で制御し、テストの決定性を保証する設計パターンを適用する。AIにFlakyでないテストを書かせるのではなく、Flakyになりようがない設計を先に作る。

7. 段階的導入がカギ——Day 1のCLAUDE.md設定から始めよ。Level 1の30分の投資から始め、効果を実感しながらLevel 2、Level 3へ進む。完璧を目指して何も始めないより、不完全でも今日から始める方が100倍価値がある。

最も大切なこと——「Clean code that works」をAIと共に

Kent Beckが提唱した「Clean code that works(動くきれいなコード)」は、TDDの究極の目標だ。この一文に、テスト駆動開発の全てが凝縮されている。

TDDは「テスト手法」ではない。それは「設計手法」だ。テストを先に書くことで、APIの使いやすさ、モジュールの独立性、責任の明確さ——これらの設計品質が自然と向上する。テストしにくいコードは、設計が悪いコードだ。TDDはこのフィードバックを最も早い段階で提供する。

AIはこの構造を増幅する。良い設計パターンがCLAUDE.mdに定義されていれば、AIはそのパターンに従って大量の高品質コードを生成する。逆に、設計の指針がなければ、AIは良いコードも悪いコードも同じ速度で量産する。TDDは、AIが増幅する方向を「良い方向」に制御するための最も効果的な仕組みだ。

テスト駆動開発とは、プログラミング中の不安をコントロールするための手法である。——Kent Beck『テスト駆動開発』

AI開発においても、この本質は変わらない。「このコードは正しく動くのか」「この変更は既存の機能を壊さないか」——これらの不安をコントロールするのがテストの役割であり、テストを最初に書くことで、不安を「コードが完成する前に」解消するのがTDDの本質だ。

AIの時代、開発速度は劇的に向上した。しかし、速度だけでは品質は生まれない。テスト駆動開発は、AI開発において「品質の床」を決める。その床が高ければ、AIがどれだけ速くコードを生成しても、品質は床以下に落ちない。逆に、テストのないAI開発は、速度と引き換えに品質の床を失い、いずれ技術的負債の山に埋もれる。

今日から始める3つのアクション

本記事を読み終えた今、すぐに実行できる3つのアクションを提案する。

1. CLAUDE.mdにTDDセクションを追加する——本記事のLevel 1テンプレートをコピーし、プロジェクトのCLAUDE.mdに追加する。所要時間は30分。これだけで、次回のClaude Code利用時からテスト品質が向上する。

2. 次の実装タスクでRed-Green-Refactorサイクルを試す——次に新しい機能を実装する際、Claude Codeに「テストファーストで実装して」と指示する。AIがRed-Green-Refactorサイクルを実行する様子を観察し、TDDのリズムを体感する。

3. Hooksで自動テスト実行を設定する——コード変更時にテストが自動実行されるHookを設定する。テストの実行を「意識的な行動」から「自動的なプロセス」に変えることで、TDDの持続性が格段に向上する。

AI×TDDは、まだ発展途上の分野だ。ツール、手法、ベストプラクティスは日々進化している。しかし、その基盤にある原則——テストファースト、小さなステップ、迅速なフィードバック——は1999年のKent Beckの提唱から変わらない。AIという新しい道具を手にした今こそ、この原則の価値を再認識し、「Clean code that works」を追求する旅を始めてほしい。

関連記事(Claude Code シリーズ)

参考文献

  1. テスト駆動開発(Kent Beck著、和田卓人訳)
  2. Claude Code 公式ドキュメント — Anthropic
  3. TDD with Claude Code — alexop.dev
  4. tdd-guard — GitHub
  5. Test-Driven Development with AI — AI Hero
  6. Vitest 公式ドキュメント
  7. Playwright 公式ドキュメント
  8. Steve Kinney — AI-Assisted TDD (Frontend Masters)

AI開発・導入のご相談

「何から始めればいいか分からない」「費用感を知りたい」など、AI導入に関するご相談を無料で承っております。大手SIerのような高額な費用は不要。経験豊富なエンジニアが直接対応します。

AIスクール受講生募集中

未経験からAIエンジニアへ。現役エンジニアによるマンツーマン指導で、実践的なAIスキルを最短で習得できます。就職・転職サポートも充実。まずは無料カウンセリングへ。

この記事をシェア