如何确定Python mock.patch的正确路径?动态场景下的困惑
问题场景
熟悉mock.patch的基础路径规则,但遇到多层动态构造的场景(比如Django Viewflow处理器)时,无法确定正确的Mock路径。
示例代码如下:
forms.py
def get_options(): # 需要Mock的函数 ... class Form(...): field = Field(choices=get_options) ...
flow.py
from forms import Form class Flow(...): start = ( flow.Start(..., form_class=Form).Next(...) )
尝试使用mock.patch('forms.get_options')但未生效,推测是get_options的引用在动态封装过程中被转移,需要找到实际调用时的引用路径。自行编写的inspect工具函数返回forms.get_options,但无法解决问题。
核心原因
Python的mock.patch基于引用替换工作:当Form类定义时,field = Field(choices=get_options)已经将get_options的引用绑定到了Field实例内部。后续Flow使用Form时,实际调用的是Field实例中保存的旧引用,而非原模块forms.get_options的当前引用,因此直接patch原模块函数无效。
定位正确Mock路径的实用方法
1. 打印函数的所有引用载体
修改get_options,通过垃圾回收模块追踪所有引用它的对象,找到实际持有该函数的载体:
import gc def get_options(): # 遍历所有引用当前函数的对象 for referrer in gc.get_referrers(get_options): if isinstance(referrer, dict): # 检查类/实例的属性字典 for key, value in referrer.items(): if value is get_options: module = referrer.get('__module__', 'unknown') obj_name = referrer.get('__name__', 'unknown') print(f"引用位置:{module}.{obj_name}.{key}") elif hasattr(referrer, '__class__'): cls_module = referrer.__class__.__module__ cls_name = referrer.__class__.__name__ print(f"引用位置:实例 {cls_module}.{cls_name}") ...
运行测试时,控制台会输出get_options被哪个对象持有,对应的路径就是需要patch的目标。
2. 输出完整调用栈上下文
改进inspect工具,打印调用get_options的完整链条,明确调用者的层级:
import inspect def get_options(): print("完整调用栈:") for frame_info in inspect.stack(): frame = frame_info.frame module_name = frame.f_globals.get('__name__', 'unknown') # 提取类名(如果是类方法/实例方法) class_name = "" if 'self' in frame.f_locals: class_name = frame.f_locals['self'].__class__.__name__ elif 'cls' in frame.f_locals: class_name = frame.f_locals['cls'].__name__ func_name = frame_info.function print(f"{module_name}.{class_name}.{func_name} (行号:{frame_info.lineno})") ...
通过完整调用栈,可以找到最上层的调用模块/类,进而确定需要patch的引用位置。
针对当前示例的具体解法
在你的Viewflow场景中,get_options被绑定到Form.field.choices,且Form被flow.Start封装为动态实例。可以直接patch流程实例中表单字段的引用:
# 假设测试中已实例化Flow对象为my_flow with mock.patch.object(my_flow.start.form_class.base_fields['field'], 'choices', return_value=[('1', '选项1'), ('2', '选项2')]): # 执行测试逻辑 ...
如果流程实例需要动态生成,也可以在flow.Start初始化后,直接替换表单字段的choices属性,达到Mock效果。
内容的提问来源于stack exchange,提问作者James Lin

