はじめに
標準で入ってるユニットテストのフレームワークである unittest を使ってみる。
Python2 では必須だった __init__.py
は Python3.3 以降であれば不要となった。
環境
Python3.6
方法1: もっともシンプルな構造
ファイル名を指定したテスト実行はできない手段です。
ディレクトリ、ファイル構造
project
├── a.py
└── tests
└── test_add.py
テスト実行方法
テスト範囲を探して、すべてをテストする
テストが実行される。実行結果は、失敗。
$ python -m unittest discover tests
F
======================================================================
FAIL: test_add (test_add.TestAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/kumar55/project/tests/test_add.py", line 10, in test_add
self.assertEqual(expected, actual)
AssertionError: 5 != -1
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (failures=1)
テスト範囲をパッケージで指定する
テストが実行される。実行結果は、失敗。
$ python -m unittest tests.test_add
F
======================================================================
FAIL: test_add (tests.test_add.TestAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/kumar55/project/tests/test_add.py", line 10, in test_add
self.assertEqual(expected, actual)
AssertionError: 5 != -1
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (failures=1)
テスト範囲をファイル名で指定する
モジュールのインポート上、テスト実行以前に失敗する。
$ python tests/test_add.py
Traceback (most recent call last):
File "tests/test_add.py", line 2, in <module>
import a
ModuleNotFoundError: No module named 'a'
コード
mkdir -pv ./project
cat <<'__PYTHON__' | tee ./project/a.py
#!/usr/bin/env python3
# coding: utf-8
def add(x, y):
return x - y
if __name__ == '__main__':
print(add(1,2))
__PYTHON__
chmod -v +x ./project/a.py
mkdir -pv ./project/tests
cat <<'__PYTHON__' | tee ./project/tests/test_add.py
import unittest
import a
class TestAdd(unittest.TestCase):
def test_add(self):
x = 2
y = 3
expected = 5
actual = a.add(x, y)
self.assertEqual(expected, actual)
if __name__ == "__main__":
unittest.main()
__PYTHON__
方法2: ファイル名を指定したテスト実行ができる方法
方法1の対処として:
- a.pyのモジュールのパスを加える
- ただし、PEP8に準拠し、import 前に sys.path への追加をしない実装とするため、pathmagic.pyにモジュールのパスを加える実装を書き、pathmagicを各テストコードでimportする。
なお、代償というわけではないが、「方法1」でパッケージ名を指定したテスト実行ができたが、これができなくなる。後述。
ディレクトリ、ファイル構造
project
├── a.py
└── tests
├── pathmagic.py ★追加
└── test_add.py
テスト実行方法
テスト範囲を探して、すべてをテストする
テストが実行される。実行結果は、失敗。
$ cd ./project
$ python -m unittest discover tests
F
======================================================================
FAIL: test_add (test_add.TestAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/kumar55/project/tests/test_add.py", line 13, in test_add
self.assertEqual(expected, actual)
AssertionError: 5 != -1
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (failures=1)
テスト範囲をパッケージで指定する
pathmagic.py は、tests に配置され、 tests はパスに含まれてないため、「方法1」で実行できたこの指定方法が失敗する。
$ python -m unittest tests.test_add
E
======================================================================
ERROR: test_add (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_add
Traceback (most recent call last):
File "/usr/lib64/python3.6/unittest/loader.py", line 153, in loadTestsFromName
module = __import__(module_name)
File "/home/kumar55/project/tests/test_add.py", line 3, in <module>
import pathmagic
ModuleNotFoundError: No module named 'pathmagic'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
ちなみに、この対処のためにさらに pathmagic.py に手を加えればいいじゃないか、と考えたくなるがむつかしい。
まず pathmagic.py に tests を加えることは対処にならない。そもそも pathmagic.py を見つけることができないという状況なので対処できない。
次に a.py か test_add.py のどちらかに手を加えることを考えるが a.py に手を加える手はテストのためだけにテスト対象に手を加えることになるので良い手段とは言いにくい。test_add.py に import を使わず、importlibを使って動的にロードすればできるはずだが、まだ試してない。ただ、できたとしても、 test_add.py の隣に test_xxx.py を置いたときに、同様のコードが増えるので悪手になりやすい。
テスト範囲をファイル名で指定する
テストが実行される。実行結果は、失敗。
$ python tests/test_add.py
F
======================================================================
FAIL: test_add (__main__.TestAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests/test_add.py", line 13, in test_add
self.assertEqual(expected, actual)
AssertionError: 5 != -1
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (failures=1)
コード
mkdir -pv ./project
cat <<'__PYTHON__' | tee ./project/a.py >/dev/null
#!/usr/bin/env python3
# coding: utf-8
def add(x, y):
return x - y
if __name__ == '__main__':
print(add(1, 2))
__PYTHON__
chmod -v +x ./project/a.py
mkdir -pv ./project/tests
cat <<'__PYTHON__' | tee ./project/tests/pathmagic.py >/dev/null
from pathlib import Path
import sys
_tests_dir = Path(__file__).parent
_base_dir = Path.resolve(_tests_dir.parent)
_mod_dir = str(Path.resolve(_base_dir))
sys.path.append(_mod_dir)
__PYTHON__
cat <<'__PYTHON__' | tee ./project/tests/test_add.py >/dev/null
#!/usr/bin/env python3
import unittest
import pathmagic
import a
class TestAdd(unittest.TestCase):
def test_add(self):
x = 2
y = 3
expected = 5
actual = a.add(x, y)
self.assertEqual(expected, actual)
if __name__ == "__main__":
unittest.main()
__PYTHON__
chmod -v +x ./project/tests/test_add.py
参考資料
Python2でユニットテストする
https://qiita.com/kumarstack55/items/f07a2c5c783ac64fbcf1
__init__.py
がなくてもpackageをimportできる
https://qiita.com/ysk24ok/items/2711295d83218c699276#__init__py%E3%81%8C%E3%81%AA%E3%81%8F%E3%81%A6%E3%82%82package%E3%82%92import%E3%81%A7%E3%81%8D%E3%82%8B
Python3.3以降の話。
PEP8 – import not at top of file with sys.path
https://stackoverflow.com/questions/36827962/pep8-import-not-at-top-of-file-with-sys-path