昨日書いた退屈な歌を歌うコードをPythonのunittestを用いてテスト&リファクタします。
テストコード
メインのコードにbeer(n)
という関数を作りn本目の歌詞の文字列を返却することにしましょう。その前提でテストコードを書きます。異常系はまあ、うん。
import unittest from beer import beer class TestBeer(unittest.TestCase): def test_beer(self): patterns = [ (99, '99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.\n'), (2, '2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n'), (1, '1 bottle of beer on the wall, 1 bottle of beer.\nTake one down and pass it around, no more bottles of beer on the wall.\n') ] for n, expected in patterns: self.assertEqual(beer(n), expected) if __name__ == '__main__': unittest.main()
main.py
はbeer.py
にリネームしてひとまず空文字を返す関数のみを定義しておきます。
def beer(n): return ''
以上でテストを実行します。
python test_beer.py F ====================================================================== FAIL: test_beer (__main__.TestBeer) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_beer.py", line 14, in test_beer self.assertEqual(beer(n), expected) AssertionError: '' != '99 bottles of beer on the wall, 99 bottle[75 chars]l.\n' + 99 bottles of beer on the wall, 99 bottles of beer. + Take one down and pass it around, 98 bottles of beer on the wall. ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1)
期待通り失敗しましたが1つめのテストケースで止まっています。
Subtest()
を使う
一行追加するだけ。
for n, expected in patterns: with self.subTest(n): self.assertEqual(beer(n), expected)
再びテスト実行します。
python test_beer.py ====================================================================== FAIL: test_beer (__main__.TestBeer) [99] ---------------------------------------------------------------------- Traceback (most recent call last): File "test_beer.py", line 15, in test_beer self.assertEqual(beer(n), expected) AssertionError: '' != '99 bottles of beer on the wall, 99 bottle[75 chars]l.\n' + 99 bottles of beer on the wall, 99 bottles of beer. + Take one down and pass it around, 98 bottles of beer on the wall. ====================================================================== FAIL: test_beer (__main__.TestBeer) [2] ---------------------------------------------------------------------- Traceback (most recent call last): File "test_beer.py", line 15, in test_beer self.assertEqual(beer(n), expected) AssertionError: '' != '2 bottles of beer on the wall, 2 bottles [71 chars]l.\n' + 2 bottles of beer on the wall, 2 bottles of beer. + Take one down and pass it around, 1 bottle of beer on the wall. ====================================================================== FAIL: test_beer (__main__.TestBeer) [1] ---------------------------------------------------------------------- Traceback (most recent call last): File "test_beer.py", line 15, in test_beer self.assertEqual(beer(n), expected) AssertionError: '' != '1 bottle of beer on the wall, 1 bottle of[76 chars]l.\n' + 1 bottle of beer on the wall, 1 bottle of beer. + Take one down and pass it around, no more bottles of beer on the wall. ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=3)
OK。3パターンともちゃんと失敗しました。これでテストのテストは完了です。
リファクタリング
beer.py
です。原型がない。
def beer(n): a = f'{n} bottles' if n>1 else '1 bottle' b = f'{n-1} bottles' if n>2 else '1 bottle' if n==2 else 'no more bottles' return f'{a} of beer on the wall, {a} of beer.\nTake one down and pass it around, {b} of beer on the wall.\n' if __name__ == '__main__': for i in range(99, 0, -1): print(beer(i)) print('No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.')
テストしてみます。
python test_beer.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
OK。寂しいですが。
あとがき
変数名はちゃんと付けたほうが良いと思いました。