本章の方針
TDDの説明で最も多い誤解は、「テストを先に書くこと」がTDDだと思われていることだ。テストを先に書くのは TDD の結果であって本質ではない。本章では、TDD が本当に何をしているのかを解き明かし、次章以降の実践に必要な思考の型を身につける。
TDDの3つのルール
TDD の発明者 Kent Beck は、TDD を3つのルールで定義している。
1. 失敗するテストを書くまで、プロダクションコードを書いてはならない。
2. 失敗させるために必要な分だけテストを書く。コンパイルエラーも失敗に含む。
3. テストを通すために必要な最小限のプロダクションコードだけを書く。
たった3行だが、この3行が生み出すリズムがTDDの全てだ。
重要なのは「最小限」という制約だ。テストもプロダクションコードも、必要な分だけ書く。一歩ずつ進む。この制約が、後述する Red-Green-Refactor のサイクルを駆動する。
Red-Green-Refactor の正体
Red-Green-Refactor は3つのフェーズを交互に繰り返すサイクルだ。
graph LR
Red["🔴 Red<br/>失敗するテストを書く"] --> Green["🟢 Green<br/>テストを通す最小のコード"]
Green --> Refactor["🔵 Refactor<br/>設計を改善する"]
Refactor --> Red
🔴 Red:「何を実現したいか」を宣言する
Red フェーズでは、まだ存在しない振る舞いをテストとして宣言する。
// 🔴 Red:この時点では Money クラスは存在しない
test('同じ通貨・同じ金額のMoneyは等しい', () => {
const a = Money.of(1000, 'JPY')
const b = Money.of(1000, 'JPY')
expect(a.equals(b)).toBe(true)
})
ここでやっているのは、テストを書くことではない。「ソフトウェアがどう振る舞うべきか」を、実行可能な形式で宣言しているのだ。
テストが失敗することを確認する(Red になる)。失敗の確認を省略してはいけない。「テストが正しく失敗すること」は、テスト自体の正しさを証明する。
🟢 Green:宣言を満たす最小のコードを書く
Green フェーズの目的は、できるだけ早くテストを通すことだ。
// 🟢 Green:最小限でテストを通す
class Money {
private constructor(
readonly amount: number,
readonly currency: string,
) {}
static of(amount: number, currency: string): Money {
return new Money(amount, currency)
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency
}
}
ここで重要なのは、「きれいなコード」を書こうとしないことだ。バリデーションも、通貨の型安全性も、まだ考えない。テストが通る最小限のコードだけを書く。
なぜか? きれいなコードは次のフェーズの仕事だからだ。
🔵 Refactor:設計を改善する
Refactor フェーズでは、振る舞いを変えずに構造を改善する。
改善の観点:
・重複を排除する
・命名を改善する
・責務を分離する
・複雑な条件を整理する
ここでの鍵は、テストがあるから安心してリファクタリングできることだ。テストは「振る舞いが変わっていないこと」の自動証明になる。
なぜこの順序なのか
「先にコードを書いてからテストを書く」方法と、「先にテストを書いてからコードを書く」方法。結果は同じに見える。なぜ順序が重要なのか。
テスト後書きの落とし穴
テストを後から書くとき、人間の脳は 「書いたコードを正当化するテスト」 を書こうとする。
// ❌ テスト後書き:実装を見てからテストを書いた
test('calculateTotal は合計を返す', () => {
const result = calculateTotal([100, 200, 300])
expect(result).toBe(600) // 実装を見て「600が正しいはず」と判断
})
このテストは一見正しいが、「仕様として600が正しいか」ではなく「実装が600を返すか」 を検証している。実装にバグがあっても、テストは通る。
自分の答案を自分で採点しているのと同じだ。
テスト先書きの効能
テストを先に書くとき、人間の脳は**「何を実現したいか」**に集中する。
// ✅ テスト先書き:実装前に「こうあるべき」を定義した
test('空の配列は合計0を返す', () => {
expect(calculateTotal([])).toBe(0)
})
test('1要素の配列はその値を返す', () => {
expect(calculateTotal([500])).toBe(500)
})
test('複数要素の配列は合計を返す', () => {
expect(calculateTotal([100, 200, 300])).toBe(600)
})
テストを先に書くことで、「空配列はどうするか」「1要素は?」「複数は?」と仕様の境界を先に考えることになる。実装コードに引きずられない。これが「テストファースト」の本質だ。
TDDは設計手法である
ここまでの説明で気づいたかもしれない。TDD は「テストをたくさん書く手法」ではない。
TDDの本質:
1. テストで「何を実現したいか」を宣言する → 仕様定義
2. 最小のコードでテストを通す → 段階的実装
3. テストに守られながらリファクタリングする → 設計改善
→ TDDは「テスト→実装→設計」のサイクルを回す設計手法だ
テストは手段であって目的ではない。TDD の真の成果物はテスト済みの設計だ。
サイクルの粒度:5分ルール
Red-Green-Refactor の1サイクルにどれくらい時間をかけるべきか。Kent Beck のガイドラインはこうだ。
1サイクル = 5〜10分
Red : 1〜3分(テストを1本書く)
Green: 1〜5分(テストを通す最小コード)
Refactor: 1〜3分(構造改善)
10分を超えたら、ステップが大きすぎる。
→ テストを小さくして、もう一度 Red からやり直す。
この粒度を守ることが、TDD のリズムを生む。1時間に6〜12回のサイクルを回すのが理想的なペースだ。
なぜ短いサイクルが重要か? 2つの理由がある。
理由1:エラーの局所化
10個の変更をまとめて行い、テストが失敗したら、10個のどれが原因か特定する必要がある。1個の変更でテストが失敗したら、原因はその1個だ。
10変更 → テスト失敗 → 原因調査に30分
1変更 → テスト失敗 → 原因は直前の数行 → 30秒で修正
理由2:心理的安全性
直前のテストが通っていれば、常に「動く状態」からの差分は数行しかない。壊れても git checkout で安全な地点に戻れる。この安心感が、大胆なリファクタリングを可能にする。
TDDの歴史的位置づけ
TDD は Kent Beck が2003年に『Test-Driven Development: By Example』で体系化した。しかし Beck 本人は「TDDを発明したのではなく再発見した」と述べている。古い文献にも「テストを先に書いてからコードを書く」というプラクティスは記録されている。
2014年:「TDD is Dead」論争
2014年、Ruby on Rails の作者 DHH が「TDD is Dead」と宣言し、Kent Beck、Martin Fowler との公開討論が行われた。DHH の主張の核心はこうだった。
DHH の批判:
・ユニットテスト駆動の設計は、不自然なコード分離を強制する
・「テストは高速でなければならない」という教義が、結合テストの軽視を生む
・テストファーストへの信仰が、システムテストの忘却を招く
この批判には正当な側面がある。TDD を教条的に適用すると、テストのためだけの不自然な抽象化や、過剰なモックが生まれる(Ch.9 アンチパターンで詳しく扱う)。
一方、Beck の反論はこうだった。
Beck の反論:
・TDDは「全てのテストをユニットテストで書け」とは言っていない
・テストの粒度は文脈に応じて選ぶべき
・TDDの核心は「フィードバックループの短縮」であり、テストの種類は手段に過ぎない
2026年の現在、この論争は成熟した結論に至っている:TDD は有効な場面で使い、そうでない場面では使わない。万能の銀の弾丸ではなく、状況に応じて選択する道具だ(Ch.10 で判断軸を提示する)。
2025年:AI時代の再評価
12年間「TDDは古い」と言われ続けてきたTDDが、2025年に再評価の波を迎えている。Kent Beck は Pragmatic Engineer のインタビューで、TDD を AI エージェントとの開発における**「スーパーパワー」**と呼んだ。
なぜ AI 時代に TDD が再び注目されているのか。この話は Ch.11 で本格的に扱う。ここでは一つだけ伏線を張っておく:AI が書いたコードを後からテストすることは、自分の答案を自分で採点することと構造的に同じ問題を抱えている。
TDDと「テスト」の違い
最後に、混同されやすい概念を整理しておく。
| TDD | テスト(一般) | |
|---|---|---|
| タイミング | コードの前に書く | コードの後に書く |
| 目的 | 設計を駆動する | バグを検出する |
| 粒度 | 1サイクル5〜10分 | テスト計画に基づく |
| 対象 | 次の1ステップの振る舞い | 仕様全体のカバレッジ |
| 成果物 | テスト済みの設計 | テストスイート |
TDD はテストの一手法ではない。テストを道具として使う設計プロセスだ。テストカバレッジを上げることが目的ではなく、「変更に自信を持てる設計」を作ることが目的だ。
本章のまとめ
| 要点 | 内容 |
|---|---|
| TDDの3ルール | ①失敗テストなしにコードを書かない ②必要分だけテストを書く ③最小限のコードで通す |
| Red-Green-Refactor | 宣言(Red)→ 実装(Green)→ 設計改善(Refactor)の反復 |
| 本質 | TDDは「テスト手法」ではなく「設計手法」 |
| サイクル粒度 | 1サイクル5〜10分。超えたらステップを小さくする |
| 歴史 | 2003年体系化 → 2014年「TDD is Dead」論争 → 2025年AI時代に再評価 |
次章では、いよいよ手を動かす。SaaS課金管理システムの「金額」を題材に、最初の Red-Green-Refactor サイクルを回してみよう。