内容
FizzBuzzを題材にTDD(テスト駆動開発)の練習をします。
Pythonのunittestを使います。
TDDとは
Test-Driven-Developmentの略です。以下のように、テストを書いて実装、テストを書いて実装を繰り返します。
- テストを書く
- コードを書く
- リファクタリングをする(今回の記事ではできていません。)
- 上記を繰り返す
TODOリスト
機能を考えてTODOリストにし、1つずつつぶしていきます。
- 入力した数字を返す
- 3の倍数 -> Fizz!
- 5の倍数 -> Buzz!
- 15の倍数 -> FizzBuzz!
TDD開始
- 入力した数字を返す
- 3の倍数 -> Fizz!
- 5の倍数 -> Buzz!
- 15の倍数 -> FizzBuzz!
まずはテストコードを書きます。unittestをimportし、unittest.TestCaseを継承したクラスを作ります。pythonのunittestの仕様で、テストのための関数は「test_」を頭につける必要があります。
実装するクラスや関数の名前、インターフェースはここで考えて決めます。
import unittest
from calcutil.fizzbuzz import FizzBuzz
class TestFizzBuzz(unittest.TestCase):
def test_normal(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(1), 1)
self.assertEqual(fb.toFizzBuzz(2), 2)
if __name__ == '__main__':
unittest.main()
最低限エラーが発生しないだけのコードを書き、テストを走らせて失敗させます。以下、ディレクトリ構成と実装したコード、テスト結果です。
.
├── calcutil
│ └── fizzbuzz.py
└── tests
└── test_fizzbuzz.py
class FizzBuzz():
def toFizzBuzz(self, num):
return None
AssertionError: None != 1
エラーを解消すべくコードを書き直し、テストを成功させます。
def toFizzBuzz(self, num):
# return None
return num
.
-------------------------
Ran 1 test in 0.002s
OK
1つ目の機能ができました。次の機能に移ります。
- 入力した数字を返す
- 3の倍数 -> Fizz!
- 5の倍数 -> Buzz!
- 15の倍数 -> FizzBuzz!
追加する機能のテストコードを追加します。
def test_fizz(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(3), 'Fizz!')
self.assertEqual(fb.toFizzBuzz(6), 'Fizz!')
AssertionError: 3 != 'Fizz!'
エラーを解消すべくコードを書き直し、テストを成功させます。なお、1つ目のテストコードも走らせているため最初の機能を壊していないことも確認できています。
def toFizzBuzz(self, num):
if (num % 3) == 0:
return 'Fizz!'
return num
..
-------------------------
Ran 2 tests in 0.001s
OK
- 入力した数字を返す
- 3の倍数 -> Fizz!
- 5の倍数 -> Buzz!
- 15の倍数 -> FizzBuzz!
同様の流れで3つ目のテストコード追加 -> テスト失敗 -> 実装 -> テスト成功まで行います。
def test_buzz(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(5), 'Buzz!')
self.assertEqual(fb.toFizzBuzz(10), 'Buzz!')
AssertionError: 5 != 'Buzz!'
def toFizzBuzz(self, num):
if (num % 3) == 0:
return 'Fizz!'
if (num % 5) == 0:
return 'Buzz!'
return num
...
-------------------------
Ran 3 tests in 0.002s
OK
- 入力した数字を返す
- 3の倍数 -> Fizz!
- 5の倍数 -> Buzz!
- 15の倍数 -> FizzBuzz!
これも同様。
def test_fizzbuzz(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(15), 'FizzBuzz!')
self.assertEqual(fb.toFizzBuzz(30), 'FizzBuzz!')
def toFizzBuzz(self, num):
if (num % 3) == 0 and (num % 5) == 0:
return 'FizzBuzz!'
if (num % 3) == 0:
return 'Fizz!'
if (num % 5) == 0:
return 'Buzz!'
return num
....
-------------------------
Ran 4 tests in 0.001s
OK
- 入力した数字を返す
- 3の倍数 -> Fizz!
- 5の倍数 -> Buzz!
- 15の倍数 -> FizzBuzz!
TODOをこなした時点でコードは以下のようになりました。
import unittest
from calcutil.fizzbuzz import FizzBuzz
class TestFizzBuzz(unittest.TestCase):
def test_normal(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(1), 1)
self.assertEqual(fb.toFizzBuzz(2), 2)
def test_fizz(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(3), 'Fizz!')
self.assertEqual(fb.toFizzBuzz(6), 'Fizz!')
def test_buzz(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(5), 'Buzz!')
self.assertEqual(fb.toFizzBuzz(10), 'Buzz!')
def test_fizzbuzz(self):
fb = FizzBuzz()
self.assertEqual(fb.toFizzBuzz(15), 'FizzBuzz!')
self.assertEqual(fb.toFizzBuzz(30), 'FizzBuzz!')
if __name__ == '__main__':
unittest.main()
class FizzBuzz():
def toFizzBuzz(self, num):
if (num % 3) == 0 and (num % 5) == 0:
return 'FizzBuzz!'
if (num % 3) == 0:
return 'Fizz!'
if (num % 5) == 0:
return 'Buzz!'
return num
感想
すごく・・・地味です。簡単なコードだとむしろ煩わしく感じます。が、小さくステップを刻むクセを付けることは、より大きく複雑なものを作る際にも活きてくると思います。
次やるときは、今回できなかったリファクタリングを絡められるといいなと思いました。