我是否可以在模拟类的方法中访问当前的unittest.TestCase
实例而无需显式传入该实例?如果没有,从那里访问TestCase
的断言辅助函数(它们是实例方法)的正确方法是什么? (有正确的方法吗?)
MagicMock
,该方法基于TestCase
方法我想测试一个函数(handle_multiple_foobars()
)是否正确使用了另一个函数(handle_one_foobar()
),因此我嘲笑handle_one_foobar()
。 '正确'这意味着handle_multiple_foobars()
将分别为每个参数调用handle_one_foobar()
。 (每个handle_one_foobar()
参数handle_multiple_foobars()
次调用。)我不关心电话的顺序。
因此,我从这开始:
import unittest
from unittest import TestCase
from unittest.mock import patch, call
def handle_one_foobar(foobar):
raise NotImplementedError()
def handle_multiple_foobars(foobars):
for foobar in reversed(foobars):
handle_one_foobar(foobar)
class FoobarHandlingTest(TestCase):
@patch('__main__.handle_one_foobar')
def test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar(
self, handle_one_foobars_mock):
foobars = ['foo', 'bar', 'foo', 'baz']
handle_multiple_foobars(foobars)
expected_calls = [call(fb) for fb in foobars]
handle_one_foobars_mock.assert_has_calls(
expected_calls,
any_order=True)
if __name__ == '__main__':
unittest.main()
但是,如果有更多对模拟函数的调用,例如
,这也会通过def handle_multiple_foobars(foobars):
handle_one_foobar('begin')
for foobar in reversed(foobars):
handle_one_foobar(foobar)
handle_one_foobar('end')
我不想要那个。
我可以轻松编写一个额外的断言(或额外的测试)来检查呼叫的总数。但我希望将此作为一个单独的条件进行测试。所以我构建了一个不同的断言:
class FoobarHandlingTest(TestCase):
@patch('__main__.handle_one_foobar')
def test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar(
self, handle_one_foobar_mock):
foobars = ['foo', 'bar', 'foo', 'baz']
handle_multiple_foobars(foobars)
expected_calls = [call(fb) for fb in foobars]
self.assertCountEqual(
handle_one_foobar_mock.mock_calls,
expected_calls)
这将很好地捕获额外的调用:
F
======================================================================
FAIL: test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar (__main__.FoobarHandlingTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.4/unittest/mock.py", line 1136, in patched
return func(*args, **keywargs)
File "convenientmock.py", line 23, in test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar
expected_calls)
AssertionError: Element counts were not equal:
First has 1, Second has 0: call('begin')
First has 1, Second has 0: call('end')
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
但是根据我的口味,这里的断言并不明显。因此,我决定将断言提取到一个新函数中。因为这个函数与assert_has_calls()
的用途非常相似,所以我觉得它应该是mock类的一个方法。扩展MagicMock
并不难,我们甚至可以使提取的方法更通用,以便指定调用顺序是否重要:
from unittest.mock import MagicMock
class MyMock(MagicMock):
def assert_has_exactly_calls(_mock_self, calls, any_order=False):
tc = TestCase()
asserter = tc.assertCountEqual if any_order else tc.assertEqual
asserter(_mock_self.mock_calls, list(calls))
当我将测试方法修饰更改为时, @patch
将使用此类而不是unittest.mock.MagicMock
来创建模拟
@patch('__main__.handle_one_foobar', new_callable=MyMock)
def test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar( # ...
然后我可以将我的断言写为
handle_one_foobar_mock.assert_has_exactly_calls(
expected_calls,
any_order=True)
但您可能已经注意到一些非常丑陋的事情:为了能够使用TestCase
实例方法assertCountEqual()
和assertEqual()
,我创建了一个虚拟TestCase
实例这不是运行测试的真实FoobarHandlingTest
实例。
我该如何避免这种情况?
显然,我可以将测试的self
传递给断言方法,但这会导致非直观的签名。 (为什么我必须告诉关于我的测试用例的断言?)