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

使用pytest assert_has_calls测试函数调用顺序报错求助

问题:assert_has_calls断言失败,函数调用顺序测试不通过

要测试main_func内部子函数的调用顺序,用pytest和unittest.mock写了测试用例,但运行时assert_has_calls断言失败,预期调用与实际调用不匹配,求解决方法或替代实现方案。

业务代码

def get_data():
    pass

def process_data(data):
    pass

def notify_admin(action):
    pass

def save_data(data):
    pass


def main_func():
    notify_admin('start')
    data = get_data()
    processed_data = process_data(data)
    save_data(processed_data)
    notify_admin('finish')

测试代码

import pytest

from unittest.mock import patch, Mock, call
from main_func import main_func


@patch('main_func.notify_admin')
@patch('main_func.get_data')
@patch('main_func.process_data')
@patch('main_func.save_data')
def test_main_func(mock_4, mock_3, mock_2, mock_1):
    execution_order = [mock_1, mock_2, mock_3, mock_4]
    order_mock = Mock()
    for order, mock in enumerate(execution_order):
        order_mock.attach_mock(mock, f'f_{order}')

    main_func()
    order_mock.assert_has_calls([
        call.f_1(),
        call.f_2(),
        call.f_3(),
        call.f_4(),
        call.f_1(),
    ])

错误信息

E               AssertionError: Calls not found.
E               Expected: [call.f_1(), call.f_2(), call.f_3(), call.f_4(), call.f_1()]
E               Actual: [call.f_1('start'),
E                call.f_2(),
E                call.f_3(<MagicMock name='mock.f_3()' id='2049968460848'>),
E                call.f_4(<MagicMock name='mock.f_2()' id='2049968489424'>),
E                call.f_1('finish')]

问题原因

  1. 参数不匹配:预期调用未包含实际调用的参数(notify_admin传了'start'/'finish',process_data和save_data接收了上游mock的返回值)
  2. 命名对应错误:enumerate从0开始计数,导致f_0对应notify_admin,但预期里误用了f_1,命名与实际mock完全不匹配
  3. patch顺序混淆:unittest.mock装饰器是从下往上绑定参数的,用序号命名mock容易搞混对应关系

解决方法

方案一:修正参数匹配与mock命名(推荐)

用ANY忽略无需关注的参数,给mock起清晰的业务名称,避免序号混淆:

import pytest
from unittest.mock import patch, Mock, call, ANY
from main_func import main_func


@patch('main_func.save_data')
@patch('main_func.process_data')
@patch('main_func.get_data')
@patch('main_func.notify_admin')
def test_main_func(mock_notify, mock_get, mock_process, mock_save):
    # 创建调用顺序跟踪mock,给每个子函数mock起明确名称
    order_tracker = Mock()
    order_tracker.attach_mock(mock_notify, 'notify')
    order_tracker.attach_mock(mock_get, 'get')
    order_tracker.attach_mock(mock_process, 'process')
    order_tracker.attach_mock(mock_save, 'save')

    main_func()

    # 验证调用顺序和必要参数,用ANY忽略process/save的具体参数值
    order_tracker.assert_has_calls([
        call.notify('start'),
        call.get(),
        call.process(ANY),
        call.save(ANY),
        call.notify('finish')
    ])

方案二:直接验证mock调用顺序(无需attach_mock)

如果觉得attach_mock麻烦,可以手动检查每个mock的调用时机:

import pytest
from unittest.mock import patch, call, ANY
from main_func import main_func


@patch('main_func.save_data')
@patch('main_func.process_data')
@patch('main_func.get_data')
@patch('main_func.notify_admin')
def test_main_func(mock_notify, mock_get, mock_process, mock_save):
    main_func()

    # 验证调用顺序:通过mock调用记录的时间戳比较先后
    assert mock_notify.call_args_list[0] < mock_get.call_args_list[0]
    assert mock_get.call_args_list[0] < mock_process.call_args_list[0]
    assert mock_process.call_args_list[0] < mock_save.call_args_list[0]
    assert mock_save.call_args_list[0] < mock_notify.call_args_list[1]

    # 验证参数正确性
    assert mock_notify.call_args_list == [call('start'), call('finish')]
    assert mock_process.call_args == call(mock_get.return_value)
    assert mock_save.call_args == call(mock_process.return_value)

内容的提问来源于stack exchange,提问作者venv

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 14:41:03