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
相关产品推荐
相关产品推荐

