考虑两个显式的MagicMocks,例如一个通过调用带有参数的方法来创建新的模拟实例,然后这些模拟作为参数传递给另一个模拟方法:
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
中找到
In [8]: foo.method_calls
Out[8]: [call.a(1, 2), call.b(3, 4)]
但是,它们与作为参数的bar.func
的使用相分离,因此无法用于检查bar.func
是否在最简单的情况下被正确调用(例如,可能有许多对foo.a
的调用与bar.func
调用无关)。
起初,我预期新的模拟a
和b
会存储传递的参数,但实际上它们不会,因为foo.a(...)
返回一个新的MagicMock,它的名称恰好是mock.a()
,但是调用和参数由foo
记录。a
不存储它们。b
也是如此。因此,当调用bar.func(a, b)
时,a
和b
的参数不存在,也不存储在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
子类的解决方案,该子类主要工作。然而,有一个案例失败了:
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)])
这一产出如下:
[call(1, 2), call(5, 6)]
[call(3, 4)]
False
我认为这是因为为foo.a
创建的模拟被重用,因此记录了一个额外的调用。我可以测试一下:
assert call(1, 2) in bar.func.call_args.args[0].parent_mock.mock_calls
但不幸的是,这并不能保证call(1, 2)
实际上是bar.func()
的一个参数。
我可以设置一个条件,即foo.a
和foo.b
都只能调用一次,但这太严格了,因为没有理由不能多次调用这些函数,而且在本例中我关心的只是对bar.func
及其参数的调用。
在我的整体问题中,我开始怀疑是否更好的方法是修补智能定制包装对象,能够记录它们自己的调用,而不是尝试使用Mocks。
发布于 2022-09-16 02:47:56
正确的是,通过调用一个Mock
对象返回的子Mock
对象没有任何链接返回到创建它的Mock
对象,从而从子对象的角度丢失了调用的记录。
可以通过使用包装器Mock
方法创建自己的__call__
子类来解决缺乏这种链接的问题,该方法返回从原始返回的Mock
对象中克隆的Mock
对象,最后是父Mock
的call
对象,以及父Mock
对象本身,存储为附加属性:
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)
这一产出如下:
call(1, 2)
call(3, 4)
call(7, 8)
True
True
https://stackoverflow.com/questions/73738875
复制相似问题