十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

pytest 断言 diff 改进解析:dict.items() 与 dict.keys() 视图比较为何能显示缺失项

pytest 断言 diff 改进解析:dict.items() 与 dict.keys() 视图比较为何能显示缺失项 pytest 断言 diff 改进解析dict.items() 与 dict.keys() 视图比较为何能显示缺失项【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest本篇基于 pytest 的 changelog 条目 12860.improvement.rst 展开当在断言中直接比较dict.items()/dict.keys()返回的视图对象、、、、时断言失败输出现在会像 set 比较那样明确指出缺失多余的具体项。读完后你将掌握 pytest 断言重写assertion rewriting如何按操作符 操作数类型分发到专用比较器、_compare_set各比较函数的语义与输出格式以及如何用 testing/test_assertion.py 中的集成测试验证这一行为。改进内容是什么changelog 原文如下Assertion diffs fordict.items()anddict.keys()comparisons (,,,,) now show which items are missing, the same way set comparisons do.翻译成开发场景Python 的dict视图天然支持集合代数运算——d1 {a: 1, b: 2} d2 {a: 1, b: 2, c: 3} d1.items() d2.items() # Trued1 的键值对是 d2 的子集 d1.keys() d2.keys() # Trued1 的键是 d2 键的真子集这类子集/超集断言常用于校验期望的最小字段集是否全部出现、实际字段是否未超纲。改动之前当这类断言失败时pytest 无法给出专用解释只能回退到通用的逐元素迭代对比开发者必须自己肉眼比对两侧数据才能定位缺失项改动之后失败输出会直接列出哪一侧多出了哪些项与 set 比较的输出风格保持一致。原理一视图对象被识别为集合改动生效的前提是d.items()和d.keys()的返回值是collections.abc.Set的注册子类型ItemsView、KeysView而非Mapping。这一点决定了 pytest 分发器会把它们路由进集合比较分支而不是映射比较分支。分发逻辑位于 src/_pytest/assertion/util.py 的assertrepr_compare中它用match语句按(左操作数, 操作符, 右操作数)三元组精确匹配try: match (left, op, right): case (_, , _): source _compare_eq_any( left, right, highlighter, verbose, assertion_text_diff_style, truncation_budget, ) case (str(), not in, str()): source _notin_text(left, right, verbose, truncation_budget) case (AbstractSet(), ! | | | | , AbstractSet()): source SET_COMPARISON_FUNCTIONSop case _: source iter(())三个关键分支值得注意(_, , _)等值比较走 src/_pytest/assertion/_compare_any.py 的_compare_eq_any其中isset(left) and isset(right)才进入_compare_eq_set而ismapping(left) and ismapping(right)进入_compare_eq_mapping实现于 src/_pytest/assertion/_compare_mapping.py(AbstractSet(), ! | | | | , AbstractSet())两侧都是集合类型含 dict 视图且操作符是子集/超集系时查表SET_COMPARISON_FUNCTIONS[op]取对应比较函数case _没有任何专用解释可用时返回空迭代器——assertrepr_compare的文档字符串也说明Yields nothing when no specialised explanation applies此时测试输出只显示原始的assert x y摘要行。原理二_compare_set 中五个操作符的语义所有子集系比较函数集中在 src/_pytest/assertion/_compare_set.py。核心是单向差集工具def _set_one_sided_diff(posn, set1, set2, highlighter): diff set1 - set2 if diff: yield fExtra items in the {posn} set: for item in diff: yield highlighter(saferepr(item))它只计算一侧相对另一侧多出来的项并逐行输出。五个操作符的映射与语义如下见该文件 L35-L101操作符对应函数失败时输出_compare_gte_setExtra items in the right set: 右侧右减左缺失项列表_compare_lte_setExtra items in the left set: 左侧左减右缺失项列表_compare_gt_set若left right输出Both sets are equal否则同报右侧多出项_compare_lt_set若left right输出Both sets are equal否则同报左侧多出项!_both_sets_are_equal仅当两侧实际相等断言不应失败却失败时输出Both sets are equal注意与的报谁方向与直觉一致x y失败说明 y 里有 x 缺的项所以报 rightx y失败说明 x 里有 y 缺的项所以报 left。而、额外处理了两侧相等这一容易困惑的失败形态集合相等时严格包含关系不成立直接告诉开发者Both sets are equal避免面对两个看似相同的集合输出无从下手。从源码结构看为何不产生专用 diffchangelog 条目把与四个子集操作符并列列出但当前代码对等号的处理路径不同SET_COMPARISON_FUNCTIONS字典src/_pytest/assertion/_compare_set.py里刻意注释掉了条目注释说明原因 cant be done here without a prior refactor because theres an additional explanation for iterable in _compare_eq_any从源码结构看视图的比较走(_, , _)分支进入_compare_eq_any再按isset/ismapping判定落到集合或迭代器比较而不是SET_COMPARISON_FUNCTIONS查表路径。换言之四个子集操作符走的是按操作符查函数表的专门路径的视图比较在等值路径中处理两者共同保证了视图比较失败时都有可读的解释输出而非裸的assert a b。实操演示用测试套件中的用例复现testing/test_assertion.py 中的test_dict_items_view_subset参数化覆盖了与两种方向是用 Pytester 直接验证该行为的官方用例pytest.mark.parametrize(op, [, ]) def test_dict_items_view_subset(self, op, pytester: Pytester) - None: dict.items() supports set-like comparisons; assert diff should show the missing items. if op : pytester.makepyfile( def test_hello(): x {a: 1, b: 2} y {a: 1, b: 2, c: 3} assert x.items() y.items() ) else: pytester.makepyfile( def test_hello(): x {a: 1, b: 2, c: 3} y {a: 1, b: 2} assert x.items() y.items() ) result pytester.runpytest() side right if op else left result.stdout.fnmatch_lines( [ *def test_hello():*, f*assert x.items() {op} y.items()*, f*E*Extra items in the {side} set:*, *E*(c, 3)*, ] )该用例断言了失败输出必须同时包含原始断言行、Extra items in the {left|right} set:提示行以及具体缺失项(c, 3)由_set_one_sided_diff经saferepr渲染。在本地跑一个等价的最小示例方向的输出大致为def test_hello(): x {a: 1, b: 2} y {a: 1, b: 2, c: 3} assert x.items() y.items() E AssertionError: assert dict_items([((a, 1), (b, 2))]) dict_items([((a, 1), (b, 2), (c, 3))]) E E Extra items in the right set: E (c, 3)把方向的两侧交换后提示行变为Extra items in the left set:——这与测试中side right if op else left的行完全对应。与直接比较两个 dict的输出差异同一份数据比较对象不同pytest 的解释器也不同assert d1 d2两侧是Mapping走_compare_eq_mappingsrc/_pytest/assertion/_compare_mapping.py按Omitting N identical items, use -vv to show、Differing items:、Left/Right contains N more items:分组展示键值差异并支持truncation_budget截断控制assert d1.items() d2.items()或d1.keys() d2.keys()两侧是 Set 视图走上文所述的集合比较路径按Extra items in the X set:逐行列出项。因此编写断言时比较两个 dict 的内容与比较两个 dict 的键/键值对子集关系是两类意图pytest 会分别给出最贴切的 diff 形态。若需要为自定义类型补充断言解释可参考pytest_assertrepr_compare钩子规范见 src/_pytest/hookspec.py以及 testing/test_assertion.py 中test_assertrepr_loaded_per_dir展示的 conftest 注册方式。小结本次改进changelog 12860.improvement.rst让dict.items()/dict.keys()的子集系比较断言与 set 比较共享同一套解释器视图对象满足AbstractSet模式匹配被路由进SET_COMPARISON_FUNCTIONS查表src/_pytest/assertion/util.py_compare_gte_set/_compare_lte_set/_compare_gt_set/_compare_lt_set分别针对、、、输出缺失项列表、额外给出Both sets are equal提示src/_pytest/assertion/_compare_set.pytest_dict_items_view_subsettesting/test_assertion.py作为集成级回归用例锁定了Extra items in the left/right set:与具体项(c, 3)的输出形态。排错子集断言失败时只需盯住Extra items in the ... set:之后的行即可直接读出缺失的键或键值对无需再展开完整 repr 逐条比对。【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表