考虑示例:
def func_b(a):
print a
def func_a():
a = [-1]
for i in xrange(0, 2):
a[0] = i
func_b(a)
以及尝试测试 func_a 和模拟 func_b 的测试函数:
import mock
from mock import call
def test_a():
from dataTransform.test import func_a
with mock.patch('dataTransform.test.func_b', autospec=True) as func_b_mock:
func_a()
func_b_mock.assert_has_calls([call(0), call(1)])
func_a 执行后,我尝试测试 func_a 是否对 func_b 进行了正确的调用,但由于在 for 循环中,我最终改变了列表,我得到:
AssertionError: Calls not found.
Expected: [call(0), call(1)]
Actual: [call([1]), call([1])]
以下工作(从
mock
导入 unittest
是 Python 3 的事情,module
是 func_a
和 func_b
所在的位置):
import mock
from mock import call
import copy
class ModifiedMagicMock(mock.MagicMock):
def _mock_call(_mock_self, *args, **kwargs):
return super(ModifiedMagicMock, _mock_self)._mock_call(*copy.deepcopy(args), **copy.deepcopy(kwargs))
它继承自
MagicMock
,并重新定义了调用行为以深度复制参数和关键字参数。
def test_a():
from module import func_a
with mock.patch('module.func_b', new_callable=ModifiedMagicMock) as func_b_mock:
func_a()
func_b_mock.assert_has_calls([call([0]), call([1])])
您可以使用
patch
参数将新类传递到 new_callable
,但它不能与 autospec
共存。请注意,您的函数使用列表调用 func_b
,因此 call(0), call(1)
必须更改为 call([0]), call([1])
。当通过调用 test_a
运行时,这不会执行任何操作(通过)。
现在我们不能同时使用
new_callable
和autospec
,因为new_callable
是一个通用工厂,但在我们的例子中只是一个MagicMock
覆盖。但是 Autospeccing 是一个非常酷的 mock
功能,我们不想失去它。
我们需要的只是为了我们的测试而将
MagicMock
替换为 ModifiedMagicMock
:我们希望避免改变所有测试的 MagicMock
行为...可能很危险。我们已经有一个工具可以做到这一点,它是 patch
,与 new
参数一起使用来替换目的地。
在这种情况下,我们使用装饰器来避免过多的缩进并使其更具可读性:
@mock.patch('module.func_b', autospec=True)
@mock.patch("mock.MagicMock", new=ModifiedMagicMock)
def test_a(func_b_mock):
from module import func_a
func_a()
func_b_mock.assert_has_calls([call([0]), call([1])])
或者:
@mock.patch("mock.MagicMock", new=ModifiedMagicMock)
def test_a():
with mock.patch('module.func_b') as func_b_mock:
from module import func_a
func_a()
func_b_mock.assert_has_calls([call([0]), call([1])])
Python 文档中现在有一个关于此主题的部分,其中包含多种建议的解决方法:https://docs.python.org/3/library/unittest.mock-examples.html#coping-with-mutable-arguments