pytest recwarn.pop() 取到最后一条警告,检查类别选择
pytest 9.1.1 的宽泛 pop 会让后出现的同级警告覆盖先前候选。时序断言可直接检查记录列表,明确类别可精确 pop,同时保留父子类别的原有选择要求。
测试依次发出 UserWarning、RuntimeWarning 和 DeprecationWarning,recwarn.pop() 却取走了最后一个,导致 assert 'third' == 'first'。这时记录器并没有倒序收集所有警告,要检查 pop() 在宽泛类别下的选择规则。
pytest 9.1.1 的正式实现仍有这条分支:候选警告属于请求类别时,如果它不是当前候选类别的子类,就替换当前候选。互不继承的同级类别也满足这个条件,因此后出现的类别会覆盖先前选择。这与文档所说的“第一个符合条件的记录”存在差异。
需要检查发出顺序时,直接读取记录序列
下面的测试说明如何检查三条警告的完整顺序,不依赖宽泛 pop(Warning) 的选择结果:
import warnings
def test_warning_order(recwarn):
warnings.warn("first", UserWarning)
warnings.warn("second", RuntimeWarning)
warnings.warn("third", DeprecationWarning)
recorded = list(recwarn)
assert [item.category for item in recorded] == [
UserWarning, RuntimeWarning, DeprecationWarning,
]
assert [str(item.message) for item in recorded] == [
"first", "second", "third",
]
这里检查的是应用实际需要保护的时序,读取列表不会从记录器里移除项目。真实测试应保留自己的预期类别、消息和数量,不要为了让断言通过把预期顺序改成错误 pop() 的输出。
如果只关心一个明确类别,可以调用 recwarn.pop(UserWarning);遇到完全相同的类别时,当前实现会立即返回最早的精确匹配。若记录里只有它的子类,仍会进入继承关系选择,不能把所有父类参数都当成简单的先进先出队列。
警告继承优先与时间顺序是两个要求
pop() 原本还要在父子类别同时符合条件时避免优先取子类,这不是普通列表的无条件 pop(0)。不要直接改 pytest 私有 _list 或在安装目录替换整个循环;那可能修好同级类别时序,却破坏已有的父子类别测试。
保留失败时的完整警告序列,分别覆盖精确类别、互不相关的类别、父子类别和没有匹配项的情况。验收还要确认过滤配置没有提前屏蔽本应记录的警告。后续依赖更新后,先对照这些场景复查,再决定是否改回宽泛 pop();仅看到一次断言成功不能证明全部继承语义都相同。


