TL;博士
我可以unittest.TestCase
从模拟类的方法中访问当前实例而不显式传递该实例吗?TestCase
如果不是,从那里访问(实例方法)的断言辅助函数的正确方法是什么?(有合适的方法吗?)
Szenario:使用我自己的基于方法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
断言方法,但这会导致非常不直观的签名。(为什么我必须对我的测试用例进行断言?)