直接用 assert 比较嵌套字典会因浮点精度、nan、键序不定、动态字段等无关差异失败,且报错信息不可读;应改用 pytest-approx 处理数值误差、deepdiff 定位差异或手动分层断言。

直接用 assert 比较嵌套字典会出什么问题?
多数人第一反应是写 assert response == expected_dict,但一旦字典里有浮点数、datetime、NaN、顺序不敏感的键(比如 JSON 解析后键序不定),或者只关心部分字段,这个断言就会在无关差异上失败。更糟的是,报错信息只显示两段超长字典 diff,根本看不出哪一层、哪个 key 不对。
用 pytest-approx 处理浮点误差和 NaN
当嵌套结构里含浮点字段(如 API 返回的精度受限数值)或 NaN(Pandas/NumPy 常见),原生 == 会把 NaN != NaN 当成真,导致误报。这时别自己写递归比较,装个轻量依赖就行:
pip install pytest-approx
然后在测试中这样用:
from pytest_approx import approx
assert response["data"]["score"] == approx(95.6, abs=1e-2)
assert response["stats"]["mean"] == approx(float("nan"))
注意:approx() 默认不处理嵌套,它只作用于单个数值;要校验整个子字典,得逐层拆解或配合其他策略。
用 deepdiff 定位真正差异点
当必须比对整个嵌套结构、又需要清晰报错时,DeepDiff 是最省心的选择。它不改变断言逻辑,只让失败信息可读:
pip install deepdiff
实操建议:
- 用
ignore_order=True忽略列表顺序(比如返回的 items 列表顺序不保证) - 用
ignore_numeric_type_changes=True避免int和float类型差异干扰(如42vs42.0) - 用
exclude_paths跳过时间戳、ID 等动态字段:exclude_paths=["root['id']", "root['updated_at']"]
示例:
from deepdiff import DeepDiff
diff = DeepDiff(
actual,
expected,
ignore_order=True,
ignore_numeric_type_changes=True,
exclude_paths=["root['request_id']"]
)
assert not diff, f"Dict mismatch: {diff}"
手动递归断言:何时该自己写,怎么写才不崩溃?
如果项目禁用第三方库,或你只关心某几层关键路径(比如只校验 response["items"][0]["price"] 和 "currency"),手动递归比全量对比更轻、更快、更可控。关键是别写成“深拷贝+全遍历”,而是按需提取 + 分层断言:
- 用
get()带默认值防KeyError:actual.get("data", {}).get("user", {}).get("name") - 对列表用
assert len(items) == 2先卡长度,再for i, item in enumerate(items): assert item["id"] == expected_ids[i] - 避免在断言里做复杂计算;先赋值变量,再断言——这样失败时能看清中间值
例子:
user = response.get("data", {}).get("user", {})
assert user.get("age") == 28
assert user.get("profile", {}).get("verified") is True
嵌套越深,越容易漏掉中间某层为 None 或空 dict;宁可多写一行 .get(..., {}),也别信“这里肯定有”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











