pytest.approx仅支持单层数值比较,不递归处理嵌套结构;断言嵌套字典中的浮点字段需逐层定位并单独使用approx,或改用deepdiff等专用工具进行结构化容差比对。

pytest.approx 只作用于单个数值,不是递归比较工具
pytest.approx 的设计目标很明确:把一个浮点数(或数字序列)和预期值做容差比较。它内部只处理 float、int、complex 和 numpy.ndarray 这类标量或一维数组,不解析字典结构,也不递归进入嵌套层级。
你写 assert response == pytest.approx(expected) 会直接报错 —— 因为 == 左右类型不匹配(dict vs Approx instance),根本走不到容差逻辑。
-
pytest.approx(3.14)可以和3.1400000001比,但不能和{"price": 3.14}比 - 即使你对整个字典调用
json.dumps()再用字符串比,也绕不开浮点精度在 JSON 序列化中被截断的问题(比如1.0000000000000002变成"1.0") - 把
pytest.approx塞进字典里(如{"score": pytest.approx(95.5)})也没用:字典的==比较不会触发每个 value 的自定义比较协议
真正要处理嵌套中的浮点字段,得先定位到那些 key,再单独比。比如:
assert resp["data"]["metrics"]["accuracy"] == pytest.approx(0.92, abs=1e-3) assert resp["summary"]["latency_ms"] == pytest.approx(120.5, abs=0.1)
DeepDiff 才是处理嵌套容差的正确工具
DeepDiff 支持对嵌套结构里的数值字段指定 ignore_order、significant_digits 或自定义 number_to_string_func,这才是面向嵌套字典的解决方案。
- 它能识别出
{"temp": 23.456789}和{"temp": 23.456}的差异,并按你设定的精度忽略 - 可配合
ignore_paths跳过时间戳、ID 等动态字段:DeepDiff(a, b, ignore_paths=["root['id']", "root['updated_at']"]) - 返回的是差异字典(如
{"values_changed": {"root['score']": {...}}}),必须显式检查:assert not DeepDiff(a, b, ...)
别用 pytest-deep-compare:它已多年未更新,PyPI 上最后版本止于 2019 年,与 pytest 8.x 的断言重写机制冲突,报错时连哪一层出问题都看不到。
嵌套字典断言失败时,先确认“比什么”而不是“怎么比”
很多失败其实不是技术限制,而是测试意图模糊:
- 你真需要比全部字段?还是只关心业务关键路径(比如
status、items、total)? - 动态字段(
id、created_at)是否已被清洗?硬编码删 key 容易漏,建议封装成 fixture:def clean_response(resp): del resp["id"]; del resp["created_at"]; return resp - 浮点字段是否真的需要容差?有些 API 明确返回整数字符串(如
"19.99"),此时应先float()再比,而非直接套approx
最常被忽略的点是:嵌套结构里混了 set 或 datetime 对象。它们无法被 == 直接比较,也不会被 pytest.approx 处理 —— 必须提前转成可序列化的类型(list(sorted(some_set))、dt.isoformat())。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











