如何在Pytest中为类的__call__魔术方法创建Spy且不修改业务代码?
解决Pytest中为__call__魔术方法创建Spy且不修改业务代码的方案
可以实现,核心是利用unittest.mock(或pytest-mock的mocker fixture)正确包装或patch目标实例/类的__call__方法,无需改动业务代码。以下是具体实现方案:
业务代码示例
假设你的业务代码结构如下:
class A: def __call__(self, x): return x * 2 class B: def __init__(self): self.a = A() def apply_a(self, value): # 隐式调用A实例的__call__方法 return self.a(value)
方案1:Spy目标实例的绑定__call__方法
直接针对B实例中已初始化的A对象,为其绑定的__call__方法创建Spy,保留原功能同时记录调用:
def test_apply_a(mocker): b = B() # 对b.a的__call__方法创建Spy spy_call = mocker.spy(b.a, '__call__') # 执行被测方法 result = b.apply_a(5) # 验证业务逻辑正确性 assert result == 10 # 验证__call__方法被正确调用 spy_call.assert_called_once_with(5)
方案2:Patch类的__call__方法并保留原功能
如果需要在A实例创建前就设置Spy,可以patch A类的__call__方法,通过side_effect保留原逻辑:
def test_apply_a(mocker): # 保存A类原__call__方法 original_call = A.__call__ # Patch A类的__call__,保留原功能并记录调用 mock_call = mocker.patch.object(A, '__call__', side_effect=original_call) b = B() result = b.apply_a(5) assert result == 10 # 注意:类的__call__是实例方法,第一个参数为实例自身b.a mock_call.assert_called_once_with(b.a, 5)
原理说明
Python中隐式调用instance()时,本质是触发instance.__call__()。上述方案要么直接包装实例的绑定__call__方法,要么通过patch类方法并保留原逻辑,都能覆盖隐式调用场景,无需修改业务代码中的调用方式。
内容的提问来源于stack exchange,提问作者Sebastian Thomas
相关产品推荐
相关产品推荐

