簡単なテストを書いてみる
utils.py
def concat(str1: str, str2: str) -> str:
if not isinstance(str1, str) and isinstance(str2, str):
raise TypeError
return str1 + str2
utils_test.py
from unittest import TestCase, main
from utils import concat
class UtilsTestCase(TestCase):
def test_good_for_concat(self):
test_cases = [
(('a', 'b'), 'ab'),
(('test', 'case'), 'testcase'),
]
for value, expected in test_cases:
with self.subTest(value):
self.assertEqual(expected, concat(value[0], value[1]))
def test_bad_for_concat(self):
test_cases = [
(('a', 2), TypeError),
((1, 'b'), TypeError),
]
for value, exception in test_cases:
with self.subTest(value):
with self.assertRaises(exception):
concat(value[0], value[1])
if __name__ == '__main__':
main()
実行結果
-
--verbose
(-v
)で詳細な出力を出せる
$ python3 -m unittest utils_test.UtilsTestCase
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
$ python3 -m unittest utils_test.UtilsTestCase --verbose
test_bad_for_concat (utils_test.UtilsTestCase) ... ok
test_good_for_concat (utils_test.UtilsTestCase) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
., F, Eって何?
- テストには3つの結果がある
- ok(.): テストに合格
- FAIL(F): テストに合格せず、AssertionError例外が発生
- ERROR(E): テストでAssertionError以外の例外が発生
ok_fail_error.py
from unittest import TestCase, main
class OkFailErrorTestCase(TestCase):
def test_ok(self):
self.assertEqual(1, 1)
def test_fail(self):
self.assertEqual(1, 2)
def test_error(self):
import requests
self.assertEqual(1, 1)
if __name__ == '__main__':
main()
実行結果
$ python3 -m unittest ok_fail_error.OkFailErrorTestCase
EF.
======================================================================
ERROR: test_error (__main__.OkFailErrorTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "ok_fail_error.py", line 5, in test_error
import requests
ModuleNotFoundError: No module named 'requests'
======================================================================
FAIL: test_fail (__main__.OkFailErrorTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "ok_fail_error.py", line 12, in test_fail
self.assertEqual(1, 2)
AssertionError: 1 != 2
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1, errors=1)
setUp, tearDownを使ってみる
-
setUp()
- 各テストメソッドの前に実行する命令を実装可能
-
tearDown()
- 各テストメソッドの後に実行する命令を実装可能
-
setUpModule()
- 一つのクラスやモジュールにつきテスト開始時に一度だけ実行する命令を実装可能
-
tearDownModule()
- 一つのクラスやモジュールにつきテスト終了時に一度だけ実行する命令を実装可能
-
setUpClass()
- 一つのクラスやモジュールにつきテスト開始時に一度だけ実行する命令を実装可能
-
tearDownClass()
- 一つのクラスやモジュールにつきテスト終了時に一度だけ実行する命令を実装可能
integration_test.py
from unittest import TestCase, main
def setUpModule():
print('* Module setup')
def tearDownModule():
print('* Module clean-up')
class IntegrationTest(TestCase):
def setUp(self):
print('** Test setup')
def tearDown(self):
print('** Test clean-up')
def test_1(self):
print('** Test 1')
def test_2(self):
print('** Test 2')
if __name__ == '__main__':
main()
実行結果
$ python3 -m unittest integration_test.IntegrationTest --verbose
* Module setup
test_1 (integration_test.IntegrationTest) ... ** Test setup
** Test 1
** Test clean-up
ok
test_2 (integration_test.IntegrationTest) ... ** Test setup
** Test 2
** Test clean-up
ok
* Module clean-up
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
integration2_test.py
from unittest import TestCase, main
class Integration2Test(TestCase):
@classmethod
def setUpClass(cls):
print('* Class setup')
@classmethod
def tearDownClass(cls):
print('* Class clean-up')
def setUp(self):
print('** Test setup')
def tearDown(self):
print('** Test clean-up')
def test_1(self):
print('** Test 1')
def test_2(self):
print('** Test 2')
if __name__ == '__main__':
main()
実行結果
$ python3 -m unittest integration2_test.Integration2Test --verbose
* Class setup
test_1 (integration2_test.Integration2Test) ... ** Test setup
** Test 1
** Test clean-up
ok
test_2 (integration2_test.Integration2Test) ... ** Test setup
** Test 2
** Test clean-up
ok
* Class clean-up
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
mock, patchを使ってみる
-
mock
- Pythonのソフトウェアテストのためのライブラリ
- テストにおいて、システムの一部を Mock オブジェクトで置き換えることで、それらがどのように使われるかをアサートすることが可能
-
patch
- 特定のモジュール内のクラスを Mock オブジェクトで一時的に置換可能
>>> from mock import Mock
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'mock'
>>> from unittest.mock import Mock
>>> m = Mock()
>>> m
<Mock id='4449643664'>
>>> m()
<Mock name='mock()' id='4449643728'>
>>> m.return_value = 25
>>> m
<Mock id='4449643664'>
>>> m()
25
>>> m.a
<Mock name='mock.a' id='4449643728'>
>>> m.a = 10
>>> m.a
10
>>> m.a.return_value = 20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'return_value'
>>>
my_class.py
import requests
class MyClass:
def fetch_json(self, url: str) -> dict:
response = requests.get(url)
return response.json()
my_class_test.py
from unittest import TestCase, main
from unittest.mock import MagicMock, call, patch
from my_class import MyClass
class MyClassTestCase(TestCase):
def mocked_requests_get(*args, **kwargs):
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
if args[0] == 'http://example.com/test.json':
return MockResponse({'key1': 'value1'}, 200)
elif args[0] == 'http://example.com/another_test.json':
return MockResponse({'key2': 'value2'}, 200)
return MockResponse(None, 404)
@patch('requests.get', side_effect=mocked_requests_get)
def test_fetch_json(self, mock_get: MagicMock):
my_class = MyClass()
json_data = my_class.fetch_json('http://example.com/test.json')
self.assertEqual(json_data, {'key1': 'value1'})
json_data = my_class.fetch_json('http://example.com/another_test.json')
self.assertEqual(json_data, {'key2': 'value2'})
json_data = my_class.fetch_json('http://no_example.com/test.json')
self.assertIsNone(json_data)
self.assertIn(
call('http://example.com/test.json'), mock_get.call_args_list
)
self.assertIn(
call('http://example.com/another_test.json'), mock_get.call_args_list
)
self.assertEqual(len(mock_get.call_args_list), 3)
if __name__ == '__main__':
main()
実行結果
$ python3 -m unittest my_class_test.MyClassTestCase --verbose
test_fetch_json (my_class_test.MyClassTestCase) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
ファイル名、クラス名、メソッド名
-
メソッド名
-
test
で始まる名前にしないといけない
-
-
ファイル名
- なんでもよい
-
--pattern
(-p
)のデフォルトがtest*.py
なのでtest_*.py
にするのが無難か
-
クラス名
- なんでもよい
- メソッド名、ファイル名に合わせて
Test*
にするのが無難か
参考