👌
テスト駆動開発(TDD)入門
テスト駆動開発(TDD)入門
はじめに
テスト駆動開発(TDD)について、概念的なものは理解していたものの、改めて整理したので備忘録します。(今度プロジェクトで試しにやってみる)
TDDとは
テスト駆動開発(TDD)は、より良いコードを書くためのプログラミング手法。TDDというとテストの手法かと思ってしまいますが、高品質なプロダクトコードを作成するための開発手法です。
TDDに関するよくある勘違い
-
❌ 「TDDはテスト手法である」
-
❌ 「TDDさえすれば他のテストは不要」
- アプリケーションの単体テストに有効
- E2Eテスト(ユーザーが一連の操作を最初から最後まで通してのテスト)などは別途考慮しないといけない
-
❌ 「TDDは単なるテスト自動化のための手法」
- TDDは設計手法としても機能する
- コードの品質向上が主目的
TDDの基本サイクル:Red-Green-Refactoring
TDDは3つのステップを繰り返すサイクルで進められるそうです:
-
Red(レッド):
- テストする機能を書く前にテストを書きます
- このテストは失敗してもいい(赤)
-
Green(グリーン):
- テストが通るように最小限のプロダクトコードを書く
- テストが成功(緑)になることを確認
-
Refactoring(リファクタリング):
- テストが通る状態を維持しながら、コードを改善する
- より良い設計、より読みやすいコードにする
このサイクルは通常、短い時間で回すことが推奨されているようです。
具体例
シンプルな電卓アプリを例に、TDDのサイクルを見ていきましょう。
Step 1: Red - 最初のテストを書く
# calculator_test.py
import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
def test_add_returns_sum_of_two_numbers(self):
calc = Calculator()
result = calc.add(2, 3)
self.assertEqual(5, result)
この時点ではCalculator
クラスは存在しないため、テストは失敗します。
Step 2: Green - 最小限の実装
# calculator.py
class Calculator:
def add(self, a, b):
return a + b
これで最初のテストが通ります。
Step 3: Refactor - テストの改善
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
def test_add_returns_sum_of_two_numbers(self):
self.assertEqual(5, self.calc.add(2, 3))
def test_add_handles_negative_numbers(self):
self.assertEqual(-2, self.calc.add(-1, -1))
def test_add_handles_zero(self):
self.assertEqual(5, self.calc.add(0, 5))
Step 4: Red - 乗算のテストを追加
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
# 加算のテスト(既存)
def test_add_returns_sum_of_two_numbers(self):
self.assertEqual(5, self.calc.add(2, 3))
def test_add_handles_negative_numbers(self):
self.assertEqual(-2, self.calc.add(-1, -1))
def test_add_handles_zero(self):
self.assertEqual(5, self.calc.add(0, 5))
# 乗算のテスト(新規追加)
def test_multiply_returns_product_of_two_numbers(self):
self.assertEqual(6, self.calc.multiply(2, 3))
def test_multiply_handles_negative_numbers(self):
self.assertEqual(-6, self.calc.multiply(2, -3))
def test_multiply_handles_zero(self):
self.assertEqual(0, self.calc.multiply(5, 0))
この時点ではmultiply
メソッドが存在しないため、テストは失敗します。
Step 5: Green - 乗算の実装を追加
class Calculator:
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
これで全てのテストが通ります。
Step 6: Refactor - テストコードの全体の整理
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
def test_calculator_operations(self):
# 加算のテストケース
add_test_cases = [
(2, 3, 5), # 通常の加算
(-1, -1, -2), # 負数の加算
(0, 5, 5) # ゼロを含む加算
]
for a, b, expected in add_test_cases:
with self.subTest(f"Testing addition {a} + {b}"):
self.assertEqual(expected, self.calc.add(a, b))
# 乗算のテストケース
multiply_test_cases = [
(2, 3, 6), # 通常の乗算
(2, -3, -6), # 負数との乗算
(5, 0, 0) # ゼロとの乗算
]
for a, b, expected in multiply_test_cases:
with self.subTest(f"Testing multiplication {a} * {b}"):
self.assertEqual(expected, self.calc.multiply(a, b))
TDDのメリットとデメリット
メリット
品質向上と開発効率:
- バグが減少
- コードの複雑度が下がる
- 重複のないコードが書ける(らしい)
- 確認作業の効率化(デバッグ工数の削減)
- テストフェーズでのバグ発見が減少するかも
デメリット
- 慣れるまでに時間がかかる
- 開発コストが大きくなる
ただし、このデメリットは中長期的には相殺されると思いました:
- デバッグ工数の削減
- バグ修正サイクルの短縮
まとめ
- TDDは単なるテスト手法ではなく、動作する綺麗なコードを書くための開発手法。
- 初期の学習コストはかかりますが、長期的には開発効率と品質の向上につながりそう
- ガチガチのウォーターフォールのプロジェクトには向いてなさそう(単体テストを先に書くため)
- TDDについてもっと理解を深めていきたいときは「実践テスト駆動開発」を読んでみるのもいいかもしれません。
https://www.amazon.co.jp/実践テスト駆動開発-Object-Oriented-SELECTION-Freeman/dp/4798124583
Discussion