Log

いろいろ

Pythonのunittestを使ってちゃんとビールを数えているか確認する

昨日書いた退屈な歌を歌うコードをPythonのunittestを用いてテスト&リファクタします。

mtzml.hatenablog.com

テストコード

メインのコードに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.pybeer.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。寂しいですが。

あとがき

変数名はちゃんと付けたほうが良いと思いました。