TDD(テスト駆動開発)の概念は理解できても、「実際にどのような順序で開発を進めるのか」「具体的なコードレベルでどうやるのか」というところで詰まる方も多いでしょう。
本記事では、TDDの中核であるRed-Green-Refactorサイクルの各ステップを、実際のコード例と合わせて詳細に解説します。
TDDの実践的な開発手順を具体的に理解することで、日常の開発に即座に取り入れられるようになるでしょう。
TDDの開発手順の全体像
それではまず、TDDの開発手順全体の流れと考え方について解説していきます。
TDDの三つのルール
Robert C. Martin(Uncle Bob)が定式化したTDDの三原則は、TDDの実践において非常に重要な指針です。
TDDの三原則(Uncle Bobのルール)
ルール1:失敗するユニットテストを書くまで、プロダクションコードを書いてはならない
ルール2:失敗を確認するのに必要な以上のテストコードを書いてはならない
ルール3:現在失敗しているテストを通過させるのに必要な以上のプロダクションコードを書いてはならない
この三原則に従うことで、テストとプロダクションコードが交互に少しずつ成長していくTDDの本来のリズムが生まれます。
TDDとBabySteps(小さなステップ)
TDDのもうひとつの重要な概念がBabySteps(ベイビーステップ)です。
一度に大きな変更を加えるのではなく、1つのテストに対して1つの最小実装を加えるという小さなサイクルを繰り返します。
小さなステップで進むことで、常にコードが動作する状態を保ち、問題が発生した場合も直前の変更が原因であると特定しやすくなります。
開発サイクルの時間感覚
TDDの各サイクル(Red→Green→Refactor)は、数分以内で回すのが理想的です。
1時間テストを書いて、1時間実装して、1時間リファクタリングするというやり方はTDDではありません。
5〜15分程度の短いサイクルを何十回も繰り返すことがTDDのリズムであり、このリズムが開発者に心地よいフロー状態をもたらします。
Redフェーズの詳細:テストを書く技術
続いては、TDDの最初のステップであるRedフェーズの具体的なテストの書き方について確認していきましょう。
テストの命名と構造(AAA パターン)
良いテストはAAA(Arrange-Act-Assert)パターンに従って書かれます。
AAAパターンの例(Python / pytest)
def test_shopping_cart_adds_item():
# Arrange(準備)
cart = ShoppingCart()
item = Item(“りんご”, price=150)
# Act(実行)
cart.add(item)
# Assert(検証)
assert cart.total() == 150
Arrange(準備)ではテストに必要なオブジェクトや状態を準備し、Act(実行)では検証したい動作を実行し、Assert(検証)では期待する結果を検証します。
1テスト1アサーションの原則
テストの品質を高めるための重要な原則が1テスト1アサーションです。
1つのテストに複数の検証(assert)を含めると、どの検証で失敗したのかわかりにくくなり、テストの失敗原因の特定が困難になります。
できる限り1つのテストで1つの振る舞いだけを検証するようにすることで、テストの意図が明確になり、失敗時の診断が容易になります。
失敗を確認する重要性
テストを書いた後は必ず実行して失敗を確認することがTDDの重要なステップです。
失敗を確認せずに実装に進むと、実はテストが何も検証していないケース(常にpassする空のテスト)を見逃す可能性があります。
「テストが失敗している状態(Red)」を確認することが、テストが意味を持っていることの証明となります。
Greenフェーズの詳細:最小限の実装
続いては、テストを通過させるGreenフェーズでの実装の考え方について確認していきましょう。
「フェイク・イット」から始める
Greenフェーズでの最初の実装は、完全な実装である必要はありません。
テストを通過させるために、最初はハードコードした値を返す「フェイク実装」から始めることが許容されます。
フェイク実装の例
テスト:assert add(2, 3) == 5
フェイク実装:
def add(a, b):
return 5 # ハードコード(フェイク)
→ テストは通過する(Green)
フェイク実装でテストを通過させた後、次のテスト(別の入力値)を追加することで、汎用的な実装が自然と要求されます。
「明白な実装」で一気に仕上げる
実装が明らかにわかっている場合は、フェイク実装を経ずに明白な実装(Obvious Implementation)を一気に書くアプローチも使えます。
単純な関数(加算・文字列の長さ取得など)では、ためらわずに正しい実装を書いてしまう方が効率的です。
フェイク実装と明白な実装を状況に応じて使い分けることが、実践的なTDDの技術です。
テストを通過させることだけに集中する
Greenフェーズでの重要な姿勢は、「テストを通過させること」だけに集中することです。
この段階でコードの美しさや最適化を考える必要はありません。
コードの品質向上はRefactorフェーズで行うものであり、Greenフェーズでの先読み実装はTDDのリズムを乱す原因となります。
Refactorフェーズの詳細:品質の向上
続いては、テストを維持しながらコードをきれいにするRefactorフェーズについて確認していきましょう。
リファクタリングのチェックリスト
Refactorフェーズで確認すべきコードの改善ポイントを整理します。
| チェック項目 | 改善の視点 |
|---|---|
| 重複コードの排除 | 同じロジックは関数・クラスに抽出 |
| 命名の改善 | 変数名・関数名・クラス名が意図を表しているか |
| 関数・クラスのサイズ | 1つの関数は1つのことだけをしているか |
| 可読性 | コードを見ただけで何をしているかわかるか |
| マジックナンバーの排除 | 意味不明な数値定数は定数に名前を付ける |
テストが守る安心感の活用
Refactorフェーズでのリファクタリングには、テストスイートが安全網として機能します。
リファクタリングの途中でテストが失敗した場合、変更直前の状態に戻ることで問題を解消できます。
この安心感が、より積極的で品質を高めるリファクタリングを可能にします。
テストコードもリファクタリングの対象
Refactorフェーズでは、プロダクションコードだけでなくテストコード自体もリファクタリングの対象となります。
テストコードが重複していたり、読みにくくなっていたりする場合は積極的に改善します。
ただし、テストコードのリファクタリング中はプロダクションコードを変更せず、テストが引き続きGreenの状態を保つことが重要です。
TDD開発手順の実践的なまとめ
続いては、TDD開発手順を実践する上での重要なポイントと、よくある落とし穴を確認していきましょう。
よくある落とし穴と対策
TDDの実践でよく陥る落とし穴を整理します。
テストを書かずに実装を先に書いてしまう誘惑は最も一般的な落とし穴です。
「ちょっとだけ先に実装してからテストを書こう」という考えは、TDDの本質を損なう最初の一歩になりがちです。
また、1サイクルが長くなりすぎることも問題で、1時間以上Red状態が続く場合は問題が大きすぎる可能性があります。
問題を小さな単位に分解することがTDDを効果的に進める鍵となります。
モブプログラミングとTDDの組み合わせ
TDDとモブプログラミング(チーム全員でひとつのコンピューターを使ってコーディングする手法)を組み合わせることで、TDDのサイクルをチームで共有し、理解を深める効果があります。
「ドライバー(キーボードを打つ人)」がRed-Green-Refactorを実践し、残りの「ナビゲーター」が次のテストを考えるというスタイルが効果的です。
TDDと継続的インテグレーション(CI)の統合
TDDで書いたテストをCIパイプラインに統合することで、コードがリポジトリにプッシュされるたびに自動でテストが実行される環境を構築できます。
この仕組みにより、テストの失敗を即座にチームに知らせる「早期警告システム」が機能し、バグの早期発見と修正のサイクルがさらに速くなります。
まとめ
本記事では、TDDの開発手順全体像、Red-Green-Refactorの各フェーズの詳細(テストの書き方・フェイク実装・リファクタリング)、よくある落とし穴と対策について解説しました。
TDDは「失敗するテストを書く→最小実装でGreenにする→品質を高めるRefactorを行う」という短いサイクルを継続的に繰り返すことで、高品質なコードを着実に積み上げていく開発手法です。
BabyStepsで小さく進み、常にGreenの状態を保ちながらリファクタリングを積み重ねることで、TDDの本来の価値を享受できるでしょう。