Python模拟方法调用参数显示列表的最后一个状态

时间:2014-04-23 23:45:32

标签: python unit-testing mocking

我有一个以列表作为参数的函数。多次调用该函数,并且每次更新某些列表值时。我用来捕获调用参数的模拟对象总是在列表中显示所有调用参数的最新值。以下代码显示了问题。

from mock import MagicMock

def multiple_calls_test():
    m = MagicMock()
    params = [0, 'some_fixed_value', 'some_fixed_value']
    for i in xrange(1,10):
        params[0] = i
        m(params)
    for args in m.call_args_list:
        print args[0][0]

multiple_calls_test()

这是输出,注意所有调用都有9作为第一个列表元素。

[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']

有没有办法强制模拟对象复制list参数而不是保持对实际列表的引用?或者为每个方法执行断言正确值的其他方法?感谢。

3 个答案:

答案 0 :(得分:7)

不幸的是,这看起来是mock库的一个缺点,而且从查看代码来看,如果不修补模拟库本身,这看起来是不可能的。但是,看起来有一种相当轻量级的方法来获得您正在寻找的效果:

import copy
from mock import MagicMock


class CopyArgsMagicMock(MagicMock):
    """
    Overrides MagicMock so that we store copies of arguments passed into calls to the
    mock object, instead of storing references to the original argument objects.
    """

    def _mock_call(_mock_self, *args, **kwargs):
        args_copy = copy.deepcopy(args)
        kwargs_copy = copy.deepcopy(kwargs)
        return super(CopyArgsMagicMock, self)._mock_call(*args_copy, **kwargs_copy)

然后(陈述显而易见的)只需将MagicMock替换为CopyArgsMagicMock,您就会看到所需的行为。

请注意,这仅针对所提供的用例进行了测试,因此这可能不是解决问题的完整而可靠的解决方案,但希望它证明是有用的。

答案 1 :(得分:1)

对于python 3.8,接受的解决方案对我而言不再有效。
但是,官方python文档中有一个解决方案:
https://docs.python.org/3/library/unittest.mock-examples.html#coping-with-mutable-arguments
您必须向下滚动才能找到以下内容:

另一种方法是创建Mock或MagicMock的子类,该子类复制(使用copy.deepcopy())参数。这是一个示例实现:

from copy import deepcopy
class CopyingMock(MagicMock):
    def __call__(self, /, *args, **kwargs):
        args = deepcopy(args)
        kwargs = deepcopy(kwargs)
        return super(CopyingMock, self).__call__(*args, **kwargs)

这对我来说适用于python 3.8。

答案 2 :(得分:0)

尝试m.mock_calls。这将列出所有已进行的呼叫。我认为这应该可行:

>>> from unittest.mock import MagicMock, call
>>> m = MagicMock()
>>> m('abc')
<MagicMock name='mock()' id='2634881401576'>
>>> m('def')
<MagicMock name='mock()' id='2634881401576'>
>>> call('abc') in m.mock_calls
True
>>> call('ghi') in m.mock_calls
False