面白いデータを探して

適当に書く。間違えていたら教えてください。

unittest で Python の単体テストをする

unittest を使って Python単体テストをする方法のメモ。


unittest

Python単体テストをする場合、unittest を使用することが出来る。

テスト対象コード

よくある fizzbuzz を例に単体テストを実施する。

# -*- coding:utf-8 -*-

# sample.py

def fizzbuzz(number: int) -> str:
    
    if type(number) != int:
        raise TypeError("number is integer type.")

    if number % 15 == 0:
        ret = "fizzbuzz"
    elif number % 3 == 0:
        ret = "fizz"
    elif number % 5 == 0:
        ret = "buzz"
    else:
        ret = str(number)
    
    return ret

テストコードの書き方

テストコードを記載したモジュールは test_sample.py とする。
まず、unittest とテスト対象のモジュール等をインポートする。

import unittest
from sample import fizzbuzz # テスト対象

次に、テスト対象となるモジュールに対して、unittest.TestCase を継承したテスト用クラスを記述する。

class TestSample(unittest.TestCase):

    def test_fizzbuzz1(self):
        self.assertEqual(fizzbuzz(1), "1")

    def test_fizzbuzz2(self):
        self.assertEqual(fizzbuzz(3), "fizz")

    ........

    def test_fizzbuzz5(self):
        with self.assertRaises(TypeError):
            fizzbuzz(1.)

その後、コマンドラインで以下を実行する。

python -m unittest test_sample

コマンドが実行されると、 unittest.TestCase を継承したクラスの test_ から始まるメソッドをテストコードとしてテストが実行される。
それぞれの実行結果が正しいか否かは assertEqual や assertRaises などの結果をテスト結果としてコマンドライン上に出力する。*1

上のコマンドの実行結果は下のようになり、テストに成功していることがわかる。

.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

また、

ret = "fizzbuzz" -> ret = "fizbuz"

の様に sample.py を書き換えてテストを実行すると

F....
======================================================================
FAIL: test_fizzbuzz1 (test_sample.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\user\work\python_unittest_sample\test_sample.py", line 9, in test_fizzbuzz1
    self.assertEqual(fizzbuzz(1), "1")
AssertionError: 1 != '1'

----------------------------------------------------------------------
Ran 5 tests in 0.002s

FAILED (failures=1)

となり、test_fizzbuzz1に失敗したことが表示される。

また、実行前に行う処理はsetUp() メソッド, 実行後に行う処理は tearDown() メソッドに記載することが出来る。

複数の処理を実行する場合

一般的に、メソッドは分岐を持つ場合がほとんどであり、それらを上の様に各メソッドに実装するとコードが長くなり面倒くさいし、テストコードにバグが紛れ込む可能性がある。
これらを防ぐために、subTest() を用いることでいくつかのテストをまとめて行うことが出来る。*2
上の備考を参考に test_sample.py を書き換えたものが下のコードだ。正常系と異常系をどのように実装するのが適切なのかは今後調べる必要がありそうだ、、、

# -*- coding:utf-8 -*-

import unittest
from sample import fizzbuzz

class TestSample(unittest.TestCase):

    def test_fizzbuzz_normal(self):
        test_case = [
            (1, "1"),
            (3, "fizz"),
            (5, "buzz"),
            (15, "fizzbuzz")
        ]
        for param, ret in test_case:
            with self.subTest(number=param):
                self.assertEqual(fizzbuzz(param), ret)

    def test_fizzbuzz_error(self):
        with self.assertRaises(TypeError):
            fizzbuzz(1.)