Python 3のunittestを使い始めるためのメモ
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でも通じる話だと思います。