6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FizzBuzzを題材にPythonでTDDの練習をする

Last updated at Posted at 2019-10-03

内容

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_」を頭につける必要があります。
実装するクラスや関数の名前、インターフェースはここで考えて決めます。

test_fizzbuzz.py
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
fizzbuzz.py
class FizzBuzz():
    def toFizzBuzz(self, num):
        return None
AssertionError: None != 1

エラーを解消すべくコードを書き直し、テストを成功させます。

fizzbuzz.py
    def toFizzBuzz(self, num):
        # return None
        return num
.
-------------------------
Ran 1 test in 0.002s

OK

1つ目の機能ができました。次の機能に移ります。

  • 入力した数字を返す
  • 3の倍数 -> Fizz!
  • 5の倍数 -> Buzz!
  • 15の倍数 -> FizzBuzz!

追加する機能のテストコードを追加します。

test_fizzbuzz.py
    def test_fizz(self):
        fb = FizzBuzz()
        self.assertEqual(fb.toFizzBuzz(3), 'Fizz!')
        self.assertEqual(fb.toFizzBuzz(6), 'Fizz!')
AssertionError: 3 != 'Fizz!'

エラーを解消すべくコードを書き直し、テストを成功させます。なお、1つ目のテストコードも走らせているため最初の機能を壊していないことも確認できています。

fizzbuzz.py
    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つ目のテストコード追加 -> テスト失敗 -> 実装 -> テスト成功まで行います。

test_fizzbuzz.py
    def test_buzz(self):
        fb = FizzBuzz()
        self.assertEqual(fb.toFizzBuzz(5),  'Buzz!')
        self.assertEqual(fb.toFizzBuzz(10), 'Buzz!')
AssertionError: 5 != 'Buzz!'
fizzbuzz.py
    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!

これも同様。

test_fizzbuzz.py
    def test_fizzbuzz(self):
        fb = FizzBuzz()
        self.assertEqual(fb.toFizzBuzz(15), 'FizzBuzz!')
        self.assertEqual(fb.toFizzBuzz(30), 'FizzBuzz!')
fizzbuzz.py
    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をこなした時点でコードは以下のようになりました。

test_fizzbuzz.py
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()
fizzbuzz.py
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

感想

すごく・・・地味です。簡単なコードだとむしろ煩わしく感じます。が、小さくステップを刻むクセを付けることは、より大きく複雑なものを作る際にも活きてくると思います。
次やるときは、今回できなかったリファクタリングを絡められるといいなと思いました。

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?