如何模拟对接收可变对象作为参数的函数的调用?

时间:2015-04-08 13:40:40

标签: python mocking

考虑例子:

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和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循环中我最终变异列表:

AssertionError: Calls not found.
Expected: [call(0), call(1)]
Actual: [call([1]), call([1])]

1 个答案:

答案 0 :(得分:2)

以下工作(来自mock的导入unittest是Python 3,modulefunc_afunc_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_callableautospec,因为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])])