pythonのunittestメモ

unittest とは

docs.python.org

pythonユニットテストをするための標準モジュールのこと。

ちなみに、pythonで関数やクラスの挙動をテストする方法として、doctest モジュールもあるが、こちらは docstring を用いて記述するものであり、あまり複雑なテストは行えない。むしろ、具体的な使用方法などを記述する目的で使用する事が多い(気がする)。

基本的な使い方

unittestを使うシンプルな例(source

import unittest  # unittestをインポート

# unittest.TestCaseを継承して、自作クラスを定義
class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')  # イコールかどうかを検証

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())  # Trueかどうかを検証
        self.assertFalse('Foo'.isupper())  # Falseかどうかを検証

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

        # s.splitのセパレータが非stringの場合を確認
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

テストコードを書く手順は、

  1. unittest をインポート
  2. unittest.TestCase を継承したクラスを定義
  3. クラスに、test[任意] メソッドを追加し、そこに検証するべき assert[任意] を書く
  4. unittest.main()を呼ぶ

注意

クラス名は任意でOKだが、メソッド名は 必ず test[任意] にしなければならない。例 test_hoge, test_fuga
また、assert[任意] はアサートメソッドであり、
例えば

  • ab がイコールかどうかを検証したい場合
    • assertEqual(a, b)
  • x が True かどうかを検証したい場合
    • assertTrue(x)
  • x が False かどうかを検証したい場合
    • assertFalse(x)

と書く。すべてのアサートメソッドは以下の通りである。

アサートメソッド 確認事項
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)

source

上のアサートメソッドでは、変数や処理結果の 等価/非等価 や、真/偽を確認することはできるが、例外発生を確認することはできない(下記を参照)。

例外が発生することを確認したい

with assertRaises(予期される例外) でチェックしたい処理を囲む。

import unittest

class MyTest(unittest.TestCase):
    
    # 文字列と整数の加算演算はTypeErrorになる
    def test_type_error(self):
        with self.assertRaises(TypeError):
            'string' + 1

if __name__ == '__main__':
    unittest.main()

複数の値を連続でテストしたい

繰り返し処理の中身でテストしたい場合のテストの書き方。サブテスト を利用する

例えば、2つのリストの中身が 順序も含めて 等しいかを確認したい場合。

ref_list = [1, 4, 3, 5, 7]
ok_list = [1, 4, 3, 5, 7]
ng_list = [1, 4, 5, 3, 7]  # 3 と 5 が入れ替わってる

普通に比較するだけだと、TrueかFalseしかわからない

print(ref_list == ok_list)
# True
print(ref_list == ng_list)
# False

サブテストを使って確認する

import unittest

class ListTest(unittest.TestCase):

    # 実際の処理を行うメソッド
    def all_equal(self, msg, list_a, list_b):
        for a, b in zip(list_a, list_b):
            with self.subTest(msg, ref=a, target=b):
                self.assertEqual(a, b)

    # OKの場合のテスト
    def test_OK(self):
        ref_list = [1, 4, 3, 5, 7]
        ok_list  = [1, 4, 3, 5, 7]
        return self.all_equal('たぶん OK', ref_list, ok_list)

   # NGのばあいのテスト
    def test_NG(self):
        ref_list = [1, 4, 3, 5, 7]
        ng_list  = [1, 4, 5, 3, 7]  # 3 と 5 が入れ替わってる
        return self.all_equal('たぶん NG', ref_list, ng_list)


if __name__ == '__main__':
    unittest.main()
test_NG (__main__.ListTest) ... test_OK (__main__.ListTest) ... ok

======================================================================
FAIL: test_NG (__main__.ListTest) [たぶん NG] (ref=3, target=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "e03.py", line 8, in all_equal
    self.assertEqual(a, b)
AssertionError: 3 != 5

======================================================================
FAIL: test_NG (__main__.ListTest) [たぶん NG] (ref=5, target=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "e03.py", line 8, in all_equal
    self.assertEqual(a, b)
AssertionError: 5 != 3

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=2)

3と5が入れ替わってることがわかる。失敗した回数も教えてくれる。

subTest の引数は1つめにメッセージ、2つめ以降にサブテストが失敗した場合に表示したいものを入れる。 source