テスト駆動開発(TDD)とは?手順・メリット・導入判断をコード例付きで解説
リリース直前にバグが大量発覚し、修正に追われる。仕様変更のたびにデグレードが発生し、影響範囲の調査だけで何時間も消える。こうした問題の根本原因は、コードの正しさを継続的に保証する仕組みが欠けている点にあります。 テスト駆動開発(TDD: Test-Driven Development)は、まずテストを書き、そのテストを通す実装を後から追加する開発手法です。Kent Beckが2002年の著書『Test-Driven Development: By Example』で体系化しました。単なる「テストを先に書く」技法ではなく、テストを軸にソフトウェアの設計を段階的に育てる開発プロセス全体を指します。 テスト駆動開発(TDD)の定義 ─ 通常のテストとの根本的な違い 一般的なソフトウェア開発では、実装を完了した後にテストを書きます。TDDはこの順序を逆転させ、実装前にテストを書くことで、コードの品質と設計を同時に改善します。 通常のテストが「すでに書いたコードが動くか確認する行為」であるのに対し、TDDのテストは「これから書くコードがどう振る舞うべきか定義する行為」です。この違いにより、TDDでは次の効果が生まれます。 コードを書く前に仕様が明確になる テストが常に存在するため、変更時の安全網として機能する テスト可能な設計を自然と選択するようになる Red-Green-Refactorの3ステップ ─ コード例で理解するTDDサイクル TDDの中核は、Red → Green → Refactorの3ステップを繰り返すサイクルです。1サイクルは数分から十数分が目安で、短く回すほど手戻りが少なくなります。 Step 1: Red ─ 失敗するテストから始める 実装したい振る舞いをテストコードとして記述します。この時点では対応する実装がないため、テストは必ず失敗(Red)します。 Python(pytest)の場合: # test_tax.py from tax_calculator import calculate_tax_inclusive def test_税込価格を正しく計算できる(): assert calculate_tax_inclusive(1000, 0.10) == 1100 TypeScript(Vitest)の場合: // tax.test.ts import { calculateTaxInclusive } from './taxCalculator'; test('税込価格を正しく計算できる', () => { expect(calculateTaxInclusive(1000, 0.10)).toBe(1100); }); この段階ではモジュール自体が存在しないため、インポートエラーでテストが失敗します。これが正常な状態です。 Step 2: Green ─ テストをパスする最小コードを書く テストを通すために必要な最小限の実装を書きます。コードの美しさや効率は気にせず、テストが通ることだけに集中します。 Python: # tax_calculator.py def calculate_tax_inclusive(price: int, tax_rate: float) -> int: return int(price * (1 + tax_rate)) TypeScript: // taxCalculator.ts export function calculateTaxInclusive(price: number, taxRate: number): number { return Math.floor(price * (1 + taxRate)); } テストを実行すると、Green(成功)に変わります。 Step 3: Refactor ─ テストを維持しながらコードを改善する テストがGreenの状態を保ちながら、コードの重複排除、命名の改善、構造の整理を行います。テストが通り続ける限り、リファクタリングは安全です。 ...