Pythonのunittest
をちゃんと使ったことがなかったので、使い方について調べてまとめました。Windows 10のAnaconda Python 3.8.5で動作確認しています。
もっとも小さな例
被テストコードとテストコードとを同一のスクリプトに記述した小さな例を以下に示します。実際はこんなことはほぼないと思いますが。
import unittest def add(x, y): return x + y class AddTest(unittest.TestCase): def test_add(self): self.assertEqual(add(1, 2), 3) def main(): unittest.main() if __name__ == '__main__': main()
ここで、被テストコードが関数add()
、テストコードがAddTest
です。
テストコードは以下の作法にのっとって作成される必要があります。
unittest.TestCase
クラスを継承したサブクラスを作成(上記のAddTest
クラス)- サブクラスの中に、
test
で始まるメソッドを作成- メソッド名が
test
で始まらないと実行されないので注意
- メソッド名が
test
で始まるメソッドの中で、self.assertEqual()
やself.assertTrue()
などを呼び出してテストを記述
このスクリプトをmain.py
として保存しpython main.py
と実行すると、テストが無事走ります。
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Pythonパッケージに対するテスト
もう少しリアルな例として、あるPythonパッケージに対してテストを行う例を考えます。
ディレクトリ構造
以下のディレクトリ構造でコードが配置されているものとします。
root_dir/ ├─comp_prog_lib/ │ │ __init__.py │ │ │ └─math/ │ prime.py │ __init__.py │ └─tests/ │ __init__.py │ └─math_tests/ test_prime.py __init__.py
root_dir
は、例えばGitのリポジトリに相当するような上位ディレクトリであるものとします。
comp_prog_lib
は、被テスト対象であるPythonパッケージです。ここでは競技プログラミングのためのライブラリという意味でcomp_prog_lib
と名付けています。comp_prog_lib
の下には、競技プログラミングのためのモジュールがある階層構造とともに配置されているものとします。
tests
は、comp_prog_lib
をテストするためのコードです。テストコードは普通ライブラリとは分離して配置したくなるはずなので、comp_prog_lib
と並べて配置します。
prime.py
のコードを以下に示します。整数n
が素数かどうかを判定する関数が実装されています。
""" 素数に関する関数を提供 """ def is_prime(n: int): """nが素数か否かを判定""" if n <= 1: return False i = 2 while i * i <= n: if n % i == 0: return False i += 1 return True
test_prime.py
のコードを以下に示します。整数1から16までの素数判定が正しく行えているかをチェックするテストが実装されています。
""" prime.pyのテストスクリプト """ import unittest from comp_prog_lib.math.prime import is_prime class IsPrimeTest(unittest.TestCase): def test_is_prime(self): primes = set([2, 3, 5, 7, 11, 13]) for i in range(1, 17): if i in primes: self.assertTrue(is_prime(i)) else: self.assertFalse(is_prime(i))
__init__.py
はすべてパッケージを構成するための特殊ファイルで、中身は空です。
Test Discoveryによるテスト実行
テストを実行するには、root_dir/
にcd
した状態で、
$ python -m unittest discover tests
と実行します。すると、tests
ディレクトリ以下に存在する、ファイル名がtest
で始まるテストスクリプトが自動で収集され実行されます。これはTest Discoveryと呼ばれる仕組みです。実行結果は以下です。
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
なお、tests/
以下のディレクトリ構造には注意が必要そうです。最初、上記でmath_tests/
と示していたディレクトリ名をmath/
にしていたのですが、この状態でテスト実行コマンドを実行すると、
E ====================================================================== ERROR: math.test_prime (unittest.loader._FailedTest) ---------------------------------------------------------------------- ImportError: Failed to import test module: math.test_prime Traceback (most recent call last): File "(環境名)\lib\unittest\loader.py", line 436, in _find_test_path module = self._get_module_from_name(name) File "(環境名)\lib\unittest\loader.py", line 377, in _get_module_from_name __import__(name) ModuleNotFoundError: No module named 'math.test_prime'; 'math' is not a package ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
というかなりわかりにくいエラーが出て失敗しました。tests/math
がcomp_prog_lib/math
と名前衝突していたのがよくなかったようで、tests/math
をtests/math_tests
と名前を変えることでエラーが消えました(参考)。正直、理屈はよくわかっていません。
ディレクトリ構造についての補足
comp_prog_lib/
パッケージのテストがうまくいった理由について説明します。Pythonは、カレントディレクトリが暗黙的にsys.path
に加えられる仕様を持ちます。なのでroot_dir/
にcd
した状態では、comp_prog_lib
がimport可能な状態になります。なのでtest_prime.py
のfrom comp_prog_lib.math.prime import is_prime
というimport文が正しく動きます。
これは場合によっては害になることもありえます。例えば、comp_prog_lib
に相当する部分をpip install
などでインストール可能なように設定していた場合、ローカルのcomp_prog_lib
をテストしているのかインストール済のcomp_prog_lib
をテストしているのかが不明瞭になります。この問題を避けるため、Pytestのドキュメント では、comp_prog_lib
をsrc
ディレクトリの下に移動させるベストプラクティスを推奨しています。これはunittest
でも通じる話だと思います。