You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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()调用,或是将模块改写为类并桩化整个类,但不确定最优方案。


解决方案

可以通过收集所有被监控函数的调用历史,直接验证调用顺序的方式解决这个问题,无需修改模块结构,也不依赖代码执行顺序的假设。

核心思路

  1. 对模块内所有需要监控的函数创建Spy(包括get_config、get_objects及其他同模块函数);
  2. 执行foo(no_run=True)后,收集所有Spy的调用记录;
  3. 按调用时间排序,验证最后一次调用的函数是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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 11:30:51