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

问题描述 投票:0回答:4

我有一个函数,它接受一个列表作为参数。该函数会被多次调用,并且每次都会更新某些列表值。我用来捕获调用参数的模拟对象始终显示所有调用参数列表中的最新值。以下代码显示了问题。

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']

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

python unit-testing mocking
4个回答
11
投票

不幸的是,这看起来是

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
投票

对于 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 有用。


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

0
投票

这是由于 call_args_list 的惰性求值造成的

funcs = [lambda x: x + a for a in [2, 3]] 不会定义两个函数,一个 +2,另一个 +3

相反,它定义了两个函数,+3

copy() 可以在调用之前发生

© www.soinside.com 2019 - 2024. All rights reserved.