首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Python模拟--如何在返回的模拟函数中存储函数参数?

Python模拟--如何在返回的模拟函数中存储函数参数?
EN

Stack Overflow用户
提问于 2022-09-16 01:15:00
回答 1查看 154关注 0票数 1

考虑两个显式的MagicMocks,例如一个通过调用带有参数的方法来创建新的模拟实例,然后这些模拟作为参数传递给另一个模拟方法:

代码语言:javascript
运行
复制
In [1]: from unittest.mock import MagicMock

In [2]: foo = MagicMock()

In [3]: bar = MagicMock()

In [4]: a = foo.a(1, 2)

In [5]: b = foo.b(3, 4)

In [6]: bar.func(a, b)
Out[6]: <MagicMock name='mock.func()' id='140383162348976'>

In [7]: bar.method_calls
Out[7]: [call.func(<MagicMock name='mock.a()' id='140383164249232'>, <MagicMock name='mock.b()' id='140383164248848'>)]

注意,bar.method_calls列表包含对函数.a.b的调用,但是传递给这些函数的参数缺失。据我所知,它们根本没有记录在bar中。它们可以在foo中找到

代码语言:javascript
运行
复制
In [8]: foo.method_calls
Out[8]: [call.a(1, 2), call.b(3, 4)]

但是,它们与作为参数的bar.func的使用相分离,因此无法用于检查bar.func是否在最简单的情况下被正确调用(例如,可能有许多对foo.a的调用与bar.func调用无关)。

起初,我预期新的模拟ab会存储传递的参数,但实际上它们不会,因为foo.a(...)返回一个新的MagicMock,它的名称恰好是mock.a(),但是调用和参数由foo记录。a不存储它们。b也是如此。因此,当调用bar.func(a, b)时,ab的参数不存在,也不存储在bar中。

是否可以以某种方式配置foo模拟以创建新的MagicMock实例来记录传递给其.a.b方法的参数?如果没有,是否可以重构代码以捕获bar中的完整调用历史记录?理想情况下,第4-6行不是测试代码,并且应该始终不知道任何模拟。

编辑:为了清楚起见,我的目标是能够测试函数bar.func是用参数foo.a(1, 2)foo.b(3, 4)调用的。这似乎与测试函数func是用参数(1, 2)(3, 4)调用的根本不同,因为bar.foo.有额外的间接性。

(下面的评论最终是在最后被接受的答案中提到的,但我把它留给后人。)

EDIT2:blhsing提供了一个涉及MagicMock子类的解决方案,该子类主要工作。然而,有一个案例失败了:

代码语言:javascript
运行
复制
class TraceableMock(MagicMock):
    def __call__(self, *args, **kwargs):
        child_mock = super().__call__(*args, **kwargs)
        child_mock.attach_mock(self, 'parent_mock')
        return child_mock

foo = TraceableMock()
bar = MagicMock()
a = foo.a(1, 2)
a2 = foo.b(5, 6)  # extra call to foo.a, unrelated to the upcoming bar.func() call
b = foo.b(3, 4)
bar.func(a, b)
print(bar.func.call_args.args[0].parent_mock.mock_calls)
print(bar.func.call_args.args[1].parent_mock.mock_calls)
print(bar.func.call_args.args[0].parent_mock.mock_calls == [call(1, 2)])

这一产出如下:

代码语言:javascript
运行
复制
[call(1, 2), call(5, 6)]
[call(3, 4)]
False

我认为这是因为为foo.a创建的模拟被重用,因此记录了一个额外的调用。我可以测试一下:

代码语言:javascript
运行
复制
assert call(1, 2) in bar.func.call_args.args[0].parent_mock.mock_calls

但不幸的是,这并不能保证call(1, 2)实际上是bar.func()的一个参数。

我可以设置一个条件,即foo.afoo.b都只能调用一次,但这太严格了,因为没有理由不能多次调用这些函数,而且在本例中我关心的只是对bar.func及其参数的调用。

在我的整体问题中,我开始怀疑是否更好的方法是修补智能定制包装对象,能够记录它们自己的调用,而不是尝试使用Mocks。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-09-16 02:47:56

正确的是,通过调用一个Mock对象返回的子Mock对象没有任何链接返回到创建它的Mock对象,从而从子对象的角度丢失了调用的记录。

可以通过使用包装器Mock方法创建自己的__call__子类来解决缺乏这种链接的问题,该方法返回从原始返回的Mock对象中克隆的Mock对象,最后是父Mockcall对象,以及父Mock对象本身,存储为附加属性:

代码语言:javascript
运行
复制
from unittest.mock import MagicMock, call

class TraceableMock(MagicMock):
    def __call__(self, *args, **kwargs):
        child_mock = super().__call__(*args, **kwargs)
        if isinstance(child_mock, TraceableMock):
            mock = TraceableMock()
            mock.__dict__ = child_mock.__dict__.copy()
            mock.created_from = self.mock_calls[-1]
            mock.attach_mock(self, 'parent_mock')            
            return mock
        return child_mock

foo = TraceableMock()
bar = MagicMock()
a = foo.a(1, 2)
a2 = foo.a(5, 6)
b = foo.b(3, 4)
c = foo(7, 8)
bar.func(a, b)

print(bar.func.call_args.args[0].created_from)
print(bar.func.call_args.args[1].created_from)
print(c.created_from)
print(bar.func.call_args.args[0].created_from == call(1, 2))
print(bar.func.call_args.args[0].parent_mock is foo.a)

这一产出如下:

代码语言:javascript
运行
复制
call(1, 2)
call(3, 4)
call(7, 8)
True
True

演示:https://replit.com/@blhsing/CalculatingIllCustomization

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73738875

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档