テスト駆動開発(TDD)

英語名 Test-Driven Development
読み方 テスト ドリブン デベロップメント
難易度
所要時間 習得に2〜4週間
提唱者 ケント・ベック
目次

ひとことで言うと
#

テストを先に書いてから、そのテストを通すコードを書く。Red(失敗)→ Green(成功)→ Refactor(整理)の小さなサイクルを繰り返すことで、テストに守られた設計の良いコードを積み上げる開発手法。

押さえておきたい用語
#

押さえておきたい用語
Red(レッド)
テストを書いた直後のテストが失敗している状態を指す。TDDサイクルの第1ステップ。テスト対象のコードがまだ存在しないので必ず失敗する。
Green(グリーン)
テストを通す最小限のコードを書いたテストが成功している状態のこと。TDDサイクルの第2ステップ。美しさは無視して「まず動く」を優先する。
Refactor(リファクター)
テストがGreenのままコードの内部構造を改善するステップのこと。重複排除、命名改善、メソッド分割などを行う。テストが安全網になる。
ユニットテスト(Unit Test)
関数やクラスなど最小単位の振る舞いを検証するテストのこと。TDDで主に書くのはこのレベルのテスト。実行が高速で、1つの機能に集中する。
テストダブル(Test Double)
テスト対象が依存する外部コンポーネントの**代替品(モック、スタブ等)**である。DB接続やAPI呼び出しを模倣して、テストを高速・独立に実行するために使う。

テスト駆動開発の全体像
#

TDD:Red→Green→Refactorの小さなサイクルを繰り返す
Red失敗するテストを書く「何を作るか」をテストで表現するGreen最小限のコードを書く美しさは無視してとにかくテストを通すRefactorコードを整理するテストがGreenのまま構造を改善する次のテストへ(数分〜十数分のサイクル)TDDがもたらす3つの価値仕様書になるテスト = 動く仕様書安全網になるリファクタリングの勇気設計を改善するテストしやすい = 良い設計小さく回して積み上げる
TDDの1サイクルフロー
1
Red
失敗するテストを1つ書く
2
Green
テストを通す最小限のコードを書く
3
Refactor
テストがGreenのままコードを整理
繰り返す
次のテストを書いてサイクルを回す

こんな悩みに効く
#

  • コードを変更するのが怖く、リファクタリングに踏み切れない
  • テストがないコードがどんどん増え、バグが後から大量に見つかる
  • 何を作るべきか曖昧なまま実装を始めて、手戻りが多い

基本の使い方
#

ステップ1: Red — まず失敗するテストを書く

実装する機能のテストを先に書き、テストが失敗する(Red)ことを確認する

  • これから実装したい振る舞いを、テストコードで表現する
  • テストは小さく、1つの振る舞いだけを検証する
  • この時点ではテスト対象のコードはまだ存在しない

ポイント: テストを書くことで「何を作るべきか」が明確になる。仕様書代わりになる。

ステップ2: Green — テストが通る最小限のコードを書く

テストを通すために、最もシンプルなコードを書く

  • 美しさや効率は無視して、とにかくテストを通すことだけに集中
  • ハードコードでもOK。まず「Green」にすることが最優先
  • 一度に大きなコードを書かない

ポイント: 「最小限」がポイント。余計な機能を入れない。

ステップ3: Refactor — コードを整理する

テストがGreenのまま、コードの構造を改善する

  • 重複を排除する
  • 命名を改善する
  • メソッドを分割する
  • テストが通り続けていることを確認しながら進める

ポイント: テストがあるからこそ安心してリファクタリングできる。これがTDDの真価。

ステップ4: サイクルを繰り返す

次の小さな機能のテストを書き、Red→Green→Refactorを繰り返す

  • 1サイクルは数分〜十数分程度
  • 小さなサイクルの積み重ねで、機能全体を構築する
  • 行き詰まったら、直前のGreenの状態に戻す

ポイント: サイクルが長くなったら、スコープを小さくする。

具体例
#

例1:ECサイトの買い物かご合計金額計算をTDDで実装する

状況: 従業員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件**。「テストは後で書く」が実行される確率は限りなく低い。先に書く方が結局速い。

例2:FinTech企業が決済APIのリファクタリングにTDDを活用する

状況: 従業員80名のFinTech企業。決済処理APIのcalculateFee()関数が800行に肥大化。手数料計算のロジックが複雑すぎて、誰も安心して修正できない状態。テストはゼロ。

TDDでのリファクタリング手順:

  1. まず既存のcalculateFee()の振る舞いをテストで記録(既存の入出力をテスト化)
  2. テスト通過を確認(ここでGreenの状態を確保)
  3. テストを安全網にしながら、800行の関数を段階的に分割

結果:

指標BeforeAfter
calculateFee()の行数800行12関数×平均40行
テストケース数0156
手数料計算バグ(月間)2.3件0.1件
新しい手数料パターン追加の工数5日0.5日
リファクタリングによる障害0件

800行→12関数×平均40行。手数料バグは月2.3件→0.1件。障害ゼロでリファクタリングを完了できたのは、先にテストで振る舞いを「固めた」からにほかならない。

例3:医療系アプリの処方箋チェック機能でTDDが患者の安全を守る

状況: 従業員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は品質手法ではなく安全装置。

やりがちな失敗パターン
#

  1. テストを大きく書きすぎる — 最初から複雑なケースのテストを書いて、Greenにするのに30分以上かかる。1テスト1分でGreenにできるサイズにする
  2. Refactorステップをスキップする — テストが通ったらすぐ次の機能に進み、コードが汚いまま蓄積される。Refactorは必須ステップ。テストがあるからこそできる
  3. 実装の詳細をテストする — 内部のメソッド呼び出し回数などをテストしてしまい、リファクタリングのたびにテストが壊れる。振る舞い(入力と出力)をテストする
  4. TDDを全コードに強制する — UIのピクセル調整やプロトタイプまでTDDでやろうとして効率が落ちる。ビジネスロジックや複雑な計算に集中して適用するのが現実的

まとめ
#

TDDは 「テストを先に書く」 という一見逆に見えるアプローチだが、実は最も合理的な開発手法の1つ。テストが仕様書になり、リファクタリングの安全網になり、設計を良くする力になる。最初は慣れないが、2週間ほど練習すれば自然にできるようになる。まずは新しい関数を1つ、TDDで書いてみよう。