问题描述
考虑示例:
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的测试函数:
And test function that tries to test func_a and mocks 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循环中,我最终对列表进行了突变:
After func_a has executed I try to test if func_a made correct calls to func_b, but since in for loop I am mutating list in the end I get:
AssertionError: Calls not found.
Expected: [call(0), call(1)]
Actual: [call([1]), call([1])]
推荐答案
以下工作原理(从unittest
导入mock
是Python 3,而module
是func_a
和func_b
所在的位置) ):
The following works (the importing mock
from unittest
is a Python 3 thing, and module
is where func_a
and func_b
are):
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
继承的,并重新定义了调用行为以深度复制参数和关键字参数.
This inherits from MagicMock
, and redefines the call behaviour to deepcopy the arguments and keyword arguments.
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])])
您可以使用new_callable
参数将新类传递到patch
中,但是它不能与autospec
共存.请注意,您的函数使用列表调用func_b
,因此必须将call(0), call(1)
更改为call([0]), call([1])
.通过调用test_a
运行时,此操作无效(通过).
You can pass the new class into patch
using the new_callable
parameter, however it cannot co-exist with autospec
. Note that your function calls func_b
with a list, so call(0), call(1)
has to be changed to call([0]), call([1])
. When run by calling test_a
, this does nothing (passes).
现在我们不能同时使用new_callable
和autospec
,因为new_callable
是通用工厂,但在我们的情况下只是MagicMock
替代.但是自动规范是非常酷的mock
的功能,我们不想失去它.
Now we cannot use both new_callable
and autospec
because new_callable
is a generic factory but in our case is just a MagicMock
override. But Autospeccing is a very cool mock
's feature, we don't want lose it.
我们需要的是将MagicMock
替换为ModifiedMagicMock
只是为了进行测试:我们希望避免在所有测试中更改MagicMock
行为...可能很危险.我们已经有一个工具可以执行此操作,它是patch
,与new
参数一起使用以替换目标位置.
What we need is replace MagicMock
by ModifiedMagicMock
just for our test: we want avoid to change MagicMock
behavior for all tests... could be dangerous. We already have a tool to do it and it is patch
, used with the new
argument to replace the destination.
在这种情况下,我们使用修饰符来避免过多的缩进并使之更具可读性:
In this case we use decorators to avoid too much indentation and make it more readable:
@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])])
这篇关于如何模拟对接收可变对象作为参数的函数的调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!