考虑示例:
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])]
解决方法:
以下工作(从unittest导入模拟是Python 3的东西,而模块是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])])
您可以使用new_callable参数将新类传递给patch,但是它不能与autospec共存.请注意,您的函数使用列表调用func_b,因此必须将call(0),call(1)更改为call([0]),call([1]).通过调用test_a运行时,此操作不执行任何操作(通过).
现在我们不能同时使用new_callable和autospec,因为new_callable是通用工厂,但在我们的情况下只是MagicMock替代.但是Autospeccing是非常酷的模拟功能,我们不想失去它.
我们需要的只是为了测试而用ModifiedMagicMock替换MagicMock:我们要避免在所有测试中都更改MagicMock的行为……可能很危险.我们已经有一个工具可以执行此操作,它是补丁程序,与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,mocking