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

Python unittest.mock中assert_has_calls捕获循环可变参数仅取最后值问题求解

问题根本原因

unittest.mock 默认仅存储可变参数(字典、列表等)的内存引用,不会自动拷贝参数值。测试代码中foo函数全程复用同一个字典对象x,每次循环仅修改该对象内部的键值,mock记录的三次调用全部指向同一个内存地址,最终三次调用的参数都会显示为循环结束后的{'val': 2}。

修复方案1:测试侧自动拷贝调用参数(官方推荐方案,无需修改业务代码)

通过给mock对象添加side_effect,在每次函数调用时对参数做深拷贝,即可保留每次调用的实际值:

from unittest.mock import call, patch
from copy import deepcopy

def foo():
    x = {}
    for i in range(3):
        x["val"] = i
        print(x)

@patch('builtins.print')
def test_foo(print_mock):
    # 定义自动拷贝参数的side_effect
    def capture_call(*args, **kwargs):
        copied_args = deepcopy(args)
        copied_kwargs = deepcopy(kwargs)
        print_mock.call(*copied_args, **copied_kwargs)
    print_mock.side_effect = capture_call

    foo()
    calls = [call({'val': 0}), call({'val': 1}), call({'val': 2})]
    print_mock.assert_has_calls(calls)

test_foo()

修复方案2:调整业务代码(可选,仅适合允许修改业务逻辑的场景)

每次打印时生成新的字典对象,避免复用同一个可变对象:

def foo():
    for i in range(3):
        x = {"val": i}
        print(x)

修改后原测试代码即可直接运行通过。

替代测试方案:捕获标准输出做断言

如果不想修改mock逻辑,可以直接捕获函数的标准输出内容验证结果,更贴合该函数的实际行为:

import io
import sys

def foo():
    x = {}
    for i in range(3):
        x["val"] = i
        print(x)

def test_foo():
    captured = io.StringIO()
    sys.stdout = captured
    foo()
    sys.stdout = sys.__stdout__
    output = captured.getvalue().strip().splitlines()
    assert output == ["{'val': 0}", "{'val': 1}", "{'val': 2}"]

test_foo()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 19:15:01