ひとことで言うと#
テストを先に書いてから、そのテストを通すコードを書く。Red(失敗)→ Green(成功)→ Refactor(整理)の小さなサイクルを繰り返すことで、テストに守られた設計の良いコードを積み上げる開発手法。
押さえておきたい用語#
- Red(レッド)
- テストを書いた直後のテストが失敗している状態を指す。TDDサイクルの第1ステップ。テスト対象のコードがまだ存在しないので必ず失敗する。
- Green(グリーン)
- テストを通す最小限のコードを書いたテストが成功している状態のこと。TDDサイクルの第2ステップ。美しさは無視して「まず動く」を優先する。
- Refactor(リファクター)
- テストがGreenのままコードの内部構造を改善するステップのこと。重複排除、命名改善、メソッド分割などを行う。テストが安全網になる。
- ユニットテスト(Unit Test)
- 関数やクラスなど最小単位の振る舞いを検証するテストのこと。TDDで主に書くのはこのレベルのテスト。実行が高速で、1つの機能に集中する。
- テストダブル(Test Double)
- テスト対象が依存する外部コンポーネントの**代替品(モック、スタブ等)**である。DB接続やAPI呼び出しを模倣して、テストを高速・独立に実行するために使う。
テスト駆動開発の全体像#
こんな悩みに効く#
- コードを変更するのが怖く、リファクタリングに踏み切れない
- テストがないコードがどんどん増え、バグが後から大量に見つかる
- 何を作るべきか曖昧なまま実装を始めて、手戻りが多い
基本の使い方#
実装する機能のテストを先に書き、テストが失敗する(Red)ことを確認する。
- これから実装したい振る舞いを、テストコードで表現する
- テストは小さく、1つの振る舞いだけを検証する
- この時点ではテスト対象のコードはまだ存在しない
ポイント: テストを書くことで「何を作るべきか」が明確になる。仕様書代わりになる。
テストを通すために、最もシンプルなコードを書く。
- 美しさや効率は無視して、とにかくテストを通すことだけに集中
- ハードコードでもOK。まず「Green」にすることが最優先
- 一度に大きなコードを書かない
ポイント: 「最小限」がポイント。余計な機能を入れない。
テストがGreenのまま、コードの構造を改善する。
- 重複を排除する
- 命名を改善する
- メソッドを分割する
- テストが通り続けていることを確認しながら進める
ポイント: テストがあるからこそ安心してリファクタリングできる。これがTDDの真価。
次の小さな機能のテストを書き、Red→Green→Refactorを繰り返す。
- 1サイクルは数分〜十数分程度
- 小さなサイクルの積み重ねで、機能全体を構築する
- 行き詰まったら、直前のGreenの状態に戻す
ポイント: サイクルが長くなったら、スコープを小さくする。
具体例#
状況: 従業員15名のEC企業のバックエンドチーム。新しいカート機能を開発することになり、TDDで実装を進める。
Red 1: テスト「空のカートの合計は0円」を書く。Cartクラスがないのでテストは失敗。
Green 1: Cartクラスを作り、total()が0を返すようにする。テスト通過。
Red 2: テスト「商品1つ(500円)をカートに入れると合計500円」を書く。add()メソッドがないのでテスト失敗。
Green 2: add()メソッドを追加し、total()が商品の合計を返すようにする。テスト通過。
Red 3: テスト「同じ商品を2つ入れると合計1000円」を書く。→ Green 3: 数量を考慮した計算に修正。
Red 4: テスト「10%割引クーポン適用で合計900円」を書く。→ Green 4: クーポンロジック追加。
Refactor: 金額計算のロジックをメソッドに切り出し、変数名を整理。全テストがGreenのままであることを確認。
| 指標 | テスト後実装 | TDD |
|---|---|---|
| 実装時間 | 2時間 | 2.5時間(+25%) |
| テストカバレッジ | 0%(後で書く予定→書かれない) | 95% |
| リリース後のバグ | 3件(割引計算ミス等) | 0件 |
実装時間**+25%、リリース後バグ0件**。「テストは後で書く」が実行される確率は限りなく低い。先に書く方が結局速い。
状況: 従業員80名のFinTech企業。決済処理APIのcalculateFee()関数が800行に肥大化。手数料計算のロジックが複雑すぎて、誰も安心して修正できない状態。テストはゼロ。
TDDでのリファクタリング手順:
- まず既存の
calculateFee()の振る舞いをテストで記録(既存の入出力をテスト化) - テスト通過を確認(ここでGreenの状態を確保)
- テストを安全網にしながら、800行の関数を段階的に分割
結果:
| 指標 | Before | After |
|---|---|---|
calculateFee()の行数 | 800行 | 12関数×平均40行 |
| テストケース数 | 0 | 156 |
| 手数料計算バグ(月間) | 2.3件 | 0.1件 |
| 新しい手数料パターン追加の工数 | 5日 | 0.5日 |
| リファクタリングによる障害 | — | 0件 |
800行→12関数×平均40行。手数料バグは月2.3件→0.1件。障害ゼロでリファクタリングを完了できたのは、先にテストで振る舞いを「固めた」からにほかならない。
状況: 従業員30名の医療IT企業。薬の処方箋を電子化するアプリで、薬の相互作用(飲み合わせ)チェック機能を開発する。誤った判定が患者の命に関わるため、バグは許されない。
TDDの適用:
- Red: 「アスピリンとワルファリンの併用は警告を出す」テストを書く
- Green: 相互作用データベースを参照して判定するコードを書く
- Red: 「用量が安全範囲内なら警告しない」テストを書く
- Green: 用量チェックロジックを追加
- Refactor: 相互作用判定と用量チェックを別クラスに分離
テストケース: 3ヶ月の開発で487のテストケースを作成(正常系185、異常系302)。
| 指標 | 業界平均 | TDD適用後 |
|---|---|---|
| 重大バグ発見(QAフェーズ) | 12件/リリース | 1件/リリース |
| 本番障害(リリース後3ヶ月) | 4.2件 | 0件 |
| テストカバレッジ | 35% | 92% |
| FDA審査での指摘事項 | 平均8件 | 2件 |
重大バグ12件→1件、本番障害0件。487のテストケースが24時間365日、処方箋チェックの正しさを検証し続けている。人命に関わるソフトウェアでは、TDDは品質手法ではなく安全装置。
やりがちな失敗パターン#
- テストを大きく書きすぎる — 最初から複雑なケースのテストを書いて、Greenにするのに30分以上かかる。1テスト1分でGreenにできるサイズにする
- Refactorステップをスキップする — テストが通ったらすぐ次の機能に進み、コードが汚いまま蓄積される。Refactorは必須ステップ。テストがあるからこそできる
- 実装の詳細をテストする — 内部のメソッド呼び出し回数などをテストしてしまい、リファクタリングのたびにテストが壊れる。振る舞い(入力と出力)をテストする
- TDDを全コードに強制する — UIのピクセル調整やプロトタイプまでTDDでやろうとして効率が落ちる。ビジネスロジックや複雑な計算に集中して適用するのが現実的
まとめ#
TDDは 「テストを先に書く」 という一見逆に見えるアプローチだが、実は最も合理的な開発手法の1つ。テストが仕様書になり、リファクタリングの安全網になり、設計を良くする力になる。最初は慣れないが、2週間ほど練習すれば自然にできるようになる。まずは新しい関数を1つ、TDDで書いてみよう。