如何模拟对接收可变对象作为参数的函数的调用? [英] How to mock calls to function that receives mutable object as parameter?

查看:74
本文介绍了如何模拟对接收可变对象作为参数的函数的调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑示例:

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,而modulefunc_afunc_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_callableautospec,因为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])])

这篇关于如何模拟对接收可变对象作为参数的函数的调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆