TDD(テスト駆動開発)とは?メリット・デメリット、Red-Green-Refactorの流れと導入ポイントを解説

テスト駆動開発(Test-Driven Development:TDD)とは、実装するコードよりも先にテストを書き、そのテストを通すための実装とリファクタリングを短いサイクルで繰り返す開発手法です。

TDDでは、「失敗するテストを書く」「テストが通る最小限のコードを書く」「コードを整理する」という流れを何度も繰り返します。このサイクルは一般に、Red(レッド)・Green(グリーン)・Refactor(リファクタリング)と呼ばれます。

TDDを適切に取り入れることで、不具合に早く気づきやすくなるだけでなく、変更しやすく保守しやすいコードを作りやすくなる点もメリットです。一方で、テストコードの作成・保守に工数がかかるため、すべての開発に向いているわけではありません。

この記事では、TDDの基本的な考え方から、通常のテストとの違い、メリット・デメリット、具体的な実施方法、向いているケース・向いていないケースまで詳しく解説します。

この記事の要点

項目内容
TDDとは実装前にテストを書き、テストを起点として開発を進める手法
基本サイクルRed → Green → Refactor
主なメリット不具合への早い気づき、変更のしやすさ、シンプルな設計につながりやすい
主なデメリットテスト作成・保守の工数、学習コスト、対象によってはテストしにくい
向いている領域ビジネスロジック、計算処理、状態遷移など
注意点TDDだけですべての品質保証を行えるわけではない

▼システム開発の流れに関する記事はこちら▼

目次

テスト駆動開発(TDD)とは

テスト駆動開発(Test-Driven Development:TDD)とは、プログラムの実装よりも先にテストコードを書き、そのテストを満たすように実装を進めていく開発手法です。

一般的な開発では、機能を実装した後にテストを書くことがあります。一方、TDDでは順番が逆です。

まず「この機能はどのように動くべきか」をテストとして表現します。その時点では機能がまだ実装されていないため、当然テストは失敗します。次に、そのテストを通すための最小限のコードを書き、最後にテストが成功する状態を維持したままコードを整理します。

この流れを小さな単位で繰り返しながら機能を完成させていくのがTDDです。

TDDはKent Beck氏の著書『Test Driven Development: By Example』などによって広く知られるようになり、エクストリームプログラミング(XP)をはじめとするアジャイルな開発とも相性のよいプラクティスとして利用されています。

ただし、TDDはアジャイル開発でしか使えない手法ではありません。短いフィードバックサイクルを取り入れたい開発であれば、開発プロセス全体の形態にかかわらず活用できます。

TDDは「テストをするためだけ」の手法ではない

TDDという名称から、単なるテスト手法だと思われることがあります。

しかしTDDの目的は、完成したシステムの不具合を発見することだけではありません。

テストを先に書くことで、「この機能にはどのような振る舞いが必要なのか」「外部からどのように利用されるべきなのか」を実装前に考えることになります。

つまりTDDでは、テストが設計を考えるためのフィードバックとしても機能します。

そのため、TDDを導入したからといって、テスターによるテストや結合テスト、システムテスト、受け入れテストなどが不要になるわけではありません。

TDDは主に開発者が実装を進める際に利用するプラクティスであり、ソフトウェア全体の品質保証は別のテスト活動と組み合わせて行う必要があります。

TDDと通常のテスト・テストファースト・ユニットテストの違い

TDDを理解するうえで迷いやすいのが、「通常のテスト」「テストファースト」「ユニットテスト」との違いです。

それぞれの違いを整理すると、次のようになります。

項目特徴
TDDテストを先に書き、Red→Green→Refactorを繰り返しながら設計・実装を進める
テストファースト実装前にテストを書く考え方。必ずしもリファクタリングまでを一連のサイクルとして扱うとは限らない
ユニットテスト関数やクラスなど小さな単位の動作を確認するテスト。実装前にも実装後にも書ける
一般的なテスト実装済みソフトウェアが仕様通り動作するか、不具合がないかを確認する活動

特に重要なのは、ユニットテストをたくさん書くこととTDDは同じではないという点です。

TDDでは、テストを書くタイミングだけでなく、そのテストから設計上のフィードバックを受け取り、実装とリファクタリングにつなげることが重視されます。

TDDの基本サイクル「Red・Green・Refactor」

TDDでは、基本的に次の3ステップを繰り返します。

Red → Green → Refactor

それぞれ詳しく見ていきましょう。

【Red】失敗するテストを書く

最初に、これから実装したい機能を表すテストコードを書きます。

まだ対象となる機能を実装していないため、この段階ではテストが失敗します。

この状態がRed(レッド)です。

重要なのは、「何となくテストを書く」のではなく、実現したい振る舞いを小さく具体的に表現することです。

たとえばECサイトで、

「購入金額が5,000円以上なら送料を無料にする」

という仕様がある場合、

「5,000円の商品を購入したとき、送料が0円になる」

というテストを先に作成します。

テストを書くことで、実装前に期待する振る舞いを明確にできます。

【Green】テストが成功する最小限のコードを書く

次に、Redで作成したテストを成功させるためのコードを実装します。

この段階がGreen(グリーン)です。

ここで重要なのは、最初から完璧な設計を目指さないことです。

まずは、目の前のテストが通るために必要な最小限の実装を行います。

一度に多くの機能を作り込むのではなく、「小さなテスト」と「小さな実装」を繰り返すことで、問題が発生した際の原因も特定しやすくなります。

【Refactor】テストを通したままコードを改善する

テストが成功したら、次にコードを整理します。

これがRefactor(リファクタリング)です。

リファクタリングとは、外部から見た動作を変えずに、コードの内部構造を改善することです。

重複した処理をまとめたり、わかりにくい変数名を変更したり、責務が大きすぎる処理を分割したりします。

このとき、すでにテストがあるため、コードを変更した結果として既存の振る舞いを壊してしまっても気づきやすくなります。

リファクタリングが完了したら、次の小さな要件について再びRedから始めます。

この短いサイクルを何度も回すことがTDDの基本です。

コード例でわかるTDDの進め方

ここでは、「5,000円以上購入した場合は送料無料」という簡単な処理を例に、TDDの流れを確認してみましょう。

以下はJavaScriptとJestを想定した簡単な例です。

1.Red:先にテストを書く

まず、送料を計算する機能そのものを実装する前にテストを書きます。

test('購入金額が5000円以上の場合、送料は0円になる’, () => {

  expect(calculateShippingFee(5000)).toBe(0);

});

この時点ではcalculateShippingFeeという関数が存在していないため、テストは失敗します。

これがRedの状態です。

2.Green:テストを通す実装を書く

次に、テストを成功させるための最小限のコードを書きます。

function calculateShippingFee(total) {

  if (total >= 5000) {

    return 0;

  }

  return 500;

}

これで先ほどのテストが成功すればGreenです。

次に、

「5,000円未満の場合は送料500円」

というテストを追加します。

test('購入金額が5000円未満の場合、送料は500円になる’, () => {

  expect(calculateShippingFee(4999)).toBe(500);

});

新しい振る舞いを追加するときも、テストから始めます。

3.Refactor:コードを整理する

テストがすべて成功した状態で、コードをより読みやすくします。

たとえば送料や送料無料になる金額を定数として分離できます。

const FREE_SHIPPING_THRESHOLD = 5000;

const STANDARD_SHIPPING_FEE = 500;

function calculateShippingFee(total) {

  return total >= FREE_SHIPPING_THRESHOLD

    ? 0

    : STANDARD_SHIPPING_FEE;

}

リファクタリング後にもう一度テストを実行し、すべて成功すれば既存の振る舞いを維持できていることを確認できます。

実際の開発では、より細かな条件や例外処理を1つずつテストとして追加しながら、機能を段階的に成長させていきます。

テスト駆動開発(TDD)のメリット

TDDには、単にテストコードが増えること以外にも複数のメリットがあります。

不具合や意図しない変更に早く気づきやすい

TDDでは小さな単位で実装とテストを繰り返します。

そのため、新しいコードを書いた直後に問題が発生すれば、どの変更が原因なのかを特定しやすくなります。

また、一度作成したテストは、その後コードを変更した際にも繰り返し実行できます。

既存機能を変更したことで別の機能が壊れてしまうデグレード(回帰不具合)にも気づきやすくなります。

ただし、テストで確認していない不具合まで自動的に発見できるわけではありません。TDDを導入すれば不具合がなくなるのではなく、変更に対するフィードバックを早く得やすくなると考えることが重要です。

システムの仕様や振る舞いを具体化できる

テストを先に書くためには、

「入力に対してどのような結果を返すのか」

「例外的な条件ではどう振る舞うのか」

といった仕様を具体的に考える必要があります。

曖昧な要件のまま実装を始めるのではなく、期待する振る舞いをテストとして表現することで、仕様に対する理解を深めやすくなります。

必要以上に複雑な実装を防ぎやすい

TDDのGreenでは、現在のテストを通すための最小限のコードを書くことを重視します。

まだ必要になっていない機能まで先回りして作り込むことを避けやすいため、実装が必要以上に複雑になるのを防ぐ効果が期待できます。

さらに、Greenの後にRefactorを行うことで、動作を維持しながらコード構造を改善できます。

コードを変更するときの不安を減らしやすい

長く運用されているシステムでは、

「このコードを変更すると、別の場所が壊れるかもしれない」

という不安から、修正をためらうことがあります。

重要な振る舞いが自動テストでカバーされていれば、変更後にテストを実行することで影響を確認できます。

もちろんテストですべてを保証できるわけではありませんが、何も確認手段がない状態と比べれば、リファクタリングや機能追加を行いやすくなります。

テストコードが仕様を理解する手掛かりになる

適切に書かれたテストコードを見ると、

「このクラスはどのように使うのか」

「この条件のときどのような結果になるのか」

といった振る舞いを確認できます。

新しいメンバーがプロジェクトに参加した際にも、テストコードが仕様を理解する補助資料として役立つことがあります。

テスト駆動開発(TDD)のデメリット

一方、TDDを導入すれば無条件に開発効率が高まるわけではありません。

導入前にはデメリットも理解しておく必要があります。

テストコードの作成・保守に工数がかかる

TDDではプロダクションコードだけでなく、テストコードも継続的に作成します。

そのため、短期的に見るとコードを書く量が増え、実装だけを行う場合より時間がかかることがあります。

また、仕様変更によって期待する振る舞いが変われば、テストコードも変更する必要があります。

特に実装の内部構造に強く依存したテストを大量に作ると、少しコードを変更しただけで多くのテスト修正が必要になる場合があります。

テストでは実装方法そのものではなく、可能な限り「外部から見た振る舞い」を確認することが重要です。

TDDの進め方を身につけるまで学習コストがかかる

テストを書く文化がないチームが突然TDDへ移行すると、開発者の負担が一時的に増える場合があります。

「どの単位でテストを書くのか」「どこまでモックを使うのか」「テストしやすい設計とは何か」など、実践を通じて身につける知識も必要です。

最初からすべての開発にTDDを適用するのではなく、小さな機能から試す方法が現実的です。

UIや外部システムとの連携など、TDDを適用しにくい領域がある

TDDは、入力と出力が明確なビジネスロジックなどと特に相性がよい手法です。

一方、画面の見た目、ユーザビリティ、外部サービスとの複雑な連携などは、ユニットテストだけで十分に確認できません。

そのため、E2Eテストや結合テスト、探索的テスト、ユーザビリティテストなど、目的に応じた別のテストと組み合わせる必要があります。

テストがあること自体が目的になってしまうことがある

TDDでは、テストコードを増やすことそのものが目的ではありません。

価値の低いテストや、実装の内部構造に依存しすぎたテストを大量に作ると、テストのメンテナンスだけが増えてしまいます。

「このテストはどのようなリスクを検知したいのか」「仕様として重要な振る舞いを確認できているか」を意識することが重要です。

TDDが向いているケース・向いていないケース

TDDはすべての開発に一律で適用するのではなく、効果を得やすい領域を選ぶことが重要です。

TDDが向いているケースTDDを適用しにくいケース
料金・税金・割引などの計算処理デザインや見た目そのものの評価
入力値のバリデーションプロトタイプを短期間で作る場合
複雑なビジネスルール仕様が極端に不確定で頻繁に破棄される試作
状態遷移を伴う処理人間による感覚的な評価が必要なUI
長期間保守する重要機能外部システムへの依存が非常に大きい処理
将来の仕様変更が多いと予想される機能一度だけ利用する小規模な処理

たとえば、ECサイトの料金計算や金融システムの利率計算などは、入力と期待する結果が比較的明確なため、TDDとの相性がよい領域です。

一方、「ボタンの配置がユーザーにとってわかりやすいか」といった問題は、自動化されたユニットテストだけで判断することはできません。

重要なのは、TDDを使うこと自体を目的にせず、開発対象に応じて適切なテスト手法を選択することです。

テスト駆動開発(TDD)を導入する際のポイント

TDDを効果的に導入するには、単に「今後はテストを先に書く」と決めるだけでは不十分です。

小さな範囲からTDDを導入する

初めてTDDに取り組むチームでは、システム全体に一度に適用しないことをおすすめします。

まずは、

「仕様が明確である」

「入力と出力がわかりやすい」

「変更される可能性が高い」

といった機能から試してみるとよいでしょう。

実践を通じてチーム内に知見が蓄積されてから対象範囲を広げる方が、導入時の負担を抑えやすくなります。

テストを高速に実行できる環境を整える

TDDではテストを何度も実行します。

1回のテストに長い時間がかかると、Red・Green・Refactorの短いフィードバックサイクルが崩れてしまいます。

ユニットテストはできるだけ高速に実行できる状態を維持し、必要に応じてCI(継続的インテグレーション)でも自動実行できる環境を整えましょう。

テスト同士を独立させる

あるテストの実行結果によって、別のテストの成否が変わってしまう状態は避けるべきです。

テスト同士に強い依存関係があると、失敗した際の原因特定が難しくなります。

データベースや外部APIなどへの依存を分離する必要がある場合には、テストダブルやモックを利用する方法があります。

ただし、モックを使いすぎると実装の内部構造に強く依存したテストになりやすいため、必要な箇所に限定することが大切です。

テストコードもレビューする

プロダクションコードと同じように、テストコードにも品質が求められます。

テスト名を読んでも何を確認しているかわからない、1つのテストで大量の条件を確認している、といった状態では保守が難しくなります。

コードレビューでは実装だけでなく、

「テストから仕様が理解できるか」

「重要な振る舞いを確認できているか」

「実装の内部構造に依存しすぎていないか」

といった点も確認するとよいでしょう。

TDDだけですべてをテストしようとしない

TDDで作成するユニットテストだけでは、システム全体の品質を確認することはできません。

複数のコンポーネントが正しく連携できるかを確認する結合テストや、実際の利用方法に近い形でシステム全体を確認するE2Eテストなども必要です。

テストピラミッドなどの考え方も参考にしながら、目的に応じて複数のテストレベルを組み合わせましょう。

TDDとBDD・ATDDの違い

TDDと一緒に語られることが多い手法として、BDD(Behavior-Driven Development)とATDD(Acceptance Test-Driven Development)があります。

名称は似ていますが、重視する対象が異なります。

手法主な目的・特徴主な参加者
TDD小さなテストを起点に設計・実装を進める主に開発者
BDD利用者から見た「振る舞い」を共通言語で表現する開発者、テスター、プロダクト担当者など
ATDD実装前に受け入れ条件を具体化し、完成条件を関係者で共有する開発者、テスター、ビジネス担当者など

BDD(ビヘイビア駆動開発)とは

BDD(Behavior-Driven Development:ビヘイビア駆動開発)は、システムが「どのように振る舞うべきか」に焦点を当てる開発アプローチです。

「Given(前提)」「When(操作)」「Then(期待結果)」などの形式を利用し、技術者以外の関係者にも理解しやすい形で仕様を表現することがあります。

TDDが開発者による設計・実装のフィードバックを強く意識するのに対し、BDDはビジネス側と開発側で期待する振る舞いを共有することを重視します。

ATDD(受け入れテスト駆動開発)とは

ATDD(Acceptance Test-Driven Development:受け入れテスト駆動開発)は、機能を開発する前に「何をもって完成とするのか」という受け入れ条件を具体化するアプローチです。

開発者だけでなく、テスターやプロダクト担当者などが受け入れ条件の作成に参加することで、認識のズレを減らすことを目指します。

TDD・BDD・ATDDはどれか1つだけを選ぶ必要があるものではなく、プロジェクトによっては組み合わせて利用されます。

なお、DDD(ドメイン駆動設計)やMDD(モデル駆動開発)なども名称に「駆動」が含まれますが、TDDとは解決しようとしている課題や対象が異なるため、別の設計・開発アプローチとして理解した方がよいでしょう。

TDDに関するよくある質問

TDDを導入すればテスターによるテストは不要になりますか?

いいえ。

TDDで開発者が作成するテストだけでは、システム全体の品質を確認することはできません。

複数機能の連携、実際のユーザー操作、ユーザビリティ、想定していなかった使い方などは、結合テストやシステムテスト、探索的テストなどによって確認する必要があります。

TDDとテスターによるテストは競合するものではなく、それぞれ異なる役割があります。

TDDはアジャイル開発でしか使えませんか?

TDDはアジャイル開発やXPで利用されることの多いプラクティスですが、アジャイル開発でなければ利用できないわけではありません。

重要なのは、テストと実装のフィードバックサイクルを小さく保つことです。

開発プロセス全体がウォーターフォール型であっても、個々の実装工程でTDDの考え方を取り入れることは可能です。

TDDを導入すると開発速度は遅くなりますか?

短期的には、テストコードを書く分だけ工数が増える場合があります。

一方で、変更の多いシステムや長期間保守するシステムでは、不具合への早い気づきやリファクタリングのしやすさが、後工程の手戻りを減らすことにつながる場合があります。

そのため、「TDDなら必ず開発速度が上がる」「必ず遅くなる」と一概には言えません。

対象となるシステムの寿命、仕様変更の頻度、チームの習熟度などを考慮して判断することが重要です。

既存システムにもTDDを導入できますか?

可能です。

ただし、既存システム全体を一度にTDDへ移行する必要はありません。

不具合を修正するときに、その不具合を再現するテストを先に追加したり、新しく追加する機能だけTDDで開発したりする方法があります。

変更頻度の高い箇所から徐々にテストを増やしていく方法が現実的です。

まとめ

テスト駆動開発(TDD)は、実装より先にテストを書き、「Red・Green・Refactor」の短いサイクルを繰り返しながら開発を進める手法です。

TDDの大きな特徴は、完成したプログラムを後からテストするだけではなく、テストを実装や設計のフィードバックとして利用する点にあります。

適切に取り入れることで、不具合や意図しない変更に早く気づきやすくなり、コードを変更・改善しやすい環境を作ることにつながります。

一方で、テストコードの作成や保守には工数がかかり、UIや外部サービスとの連携などTDDを適用しにくい領域もあります。

そのため、すべての機能に一律でTDDを導入するのではなく、複雑なビジネスロジックや長期間保守する重要機能など、効果を得やすい領域から取り入れることが大切です

また、TDDだけですべての品質を保証することはできません。結合テストやシステムテスト、E2Eテスト、探索的テストなどと組み合わせ、それぞれの目的に適した方法で品質を確認していきましょう。

QA業務効率化ならPractiTest

テスト管理の効率化についてお悩みではありませんか?そんなときはテスト資産の一元管理をすることで工数を20%削減できる総合テスト管理ツール「PractiTest」がおすすめです!

PractiTest(プラクティテスト)に関する
お問い合わせ

トライアルアカウントお申し込みや、製品デモの依頼、
機能についての問い合わせなどお気軽にお問い合わせください。

この記事の監修

Dr.T。テストエンジニア。
PractiTestエバンジェリスト。
大学卒業後、外車純正Navi開発のテストエンジニアとしてキャリアをスタート。DTVチューナ開発会社、第三者検証会社等、数々のプロダクトの検証業務に従事。
2017年株式会社モンテカンポへ入社し、マネージメント業務の傍ら、自らもテストエンジニアとしテストコンサルやPractiTestの導入サポートなどを担当している。

記事制作:川上サトシ(マーケター、合同会社ぎあはーと代表)