如何用Python Mockito验证模块中仅指定函数被调用?
问题:如何用Python Mockito验证模块内函数的调用顺序?
我有一个仅包含工具函数的简单Python模块(无类),其中函数foo()会调用同模块的多个函数,代码示例如下:
def get_config(args): ... return config_dictionary def get_objs(args): ... return list_of_objects def foo(no_run=False): config = get_config(...) if no_run: return XYZ objs = get_objects(config) for obj in objs: obj.work() ... # 调用同模块的其他多个函数
我需要验证:当传入特定参数调用foo(no_run=True)时,get_config()是该模块中最后被调用的函数。
当前的验证方式存在缺陷:
spy2(mymodule.get_config) spy2(mymodule.get_objects) assert foo(no_run=True) == XYZ verify(mymodule).get_config(...) # 假设get_objects()是get_config之后第一个会被调用的函数 verify(mymodule, times=0).get_objects(...)
这种方式依赖对代码执行顺序的假设,如果foo()内代码重排(比如在get_config之后新增/调整了其他模块函数的调用),测试仍会通过,这不符合我的需求。我考虑过动态生成spy()和verify()调用,或是将模块改写为类并桩化整个类,但不确定最优方案。
解决方案
可以通过收集所有被监控函数的调用历史,直接验证调用顺序的方式解决这个问题,无需修改模块结构,也不依赖代码执行顺序的假设。
核心思路
- 对模块内所有需要监控的函数创建Spy(包括
get_config、get_objects及其他同模块函数); - 执行
foo(no_run=True)后,收集所有Spy的调用记录; - 按调用时间排序,验证最后一次调用的函数是
get_config。
具体实现代码
from mockito import spy2, verify, unstub import inspect import mymodule def test_foo_no_run_last_call_is_get_config(): # 动态获取模块内所有工具函数(排除foo本身),批量创建Spy module_funcs = [ func for name, func in inspect.getmembers(mymodule, inspect.isfunction) if name != "foo" ] for func in module_funcs: spy2(func) try: # 执行被测函数 result = mymodule.foo(no_run=True) assert result == XYZ # 收集所有被调用函数的最后一次调用时间 call_history = [] for name, func in inspect.getmembers(mymodule, inspect.isfunction): if name == "foo": continue if hasattr(func, "invocations") and func.invocations: # 取该函数最后一次调用的时间戳 last_invocation = func.invocations[-1] call_history.append((name, last_invocation.timestamp)) # 验证确实有函数被调用 assert call_history, "模块内无函数被调用" # 按调用时间排序,取最后一个调用的函数 call_history.sort(key=lambda x: x[1]) last_called = call_history[-1][0] assert last_called == "get_config", f"最后调用的函数是{last_called},预期为get_config" # 额外验证get_config的调用参数符合预期 verify(mymodule).get_config(...) finally: # 清理桩对象,避免影响其他测试 unstub()
方案优势
- 不依赖代码结构假设:无论
foo()内代码如何重排,只要get_config不是最后一个被调用的模块函数,测试就会失败; - 扩展性强:动态遍历模块函数,新增工具函数时无需修改测试代码;
- 无需修改原模块:不用将工具模块改为类结构,保持原有代码风格。
内容的提问来源于stack exchange,提问作者Vlad
相关产品推荐
相关产品推荐

