应使用 warnings.catch_warnings(record=true) 或 pytest.warns 捕获自定义 warning;需确保警告类继承 warning、设 record=true 或 simplefilter("always"),且 pytest 配置未忽略警告。

用 warnings.catch_warnings 临时捕获自定义 Warning
Python 的 warnings 模块默认不抛出异常,即使你触发了自定义警告类,测试里也看不到它——除非显式捕获。最直接的方式是用 warnings.catch_warnings(record=True) 上下文管理器,它能拦截所有警告并存为列表。
注意:必须设 record=True,否则 warnings 对象为空;还要确保你的自定义警告类继承自 Warning(不能是 Exception 或普通 object)。
- 示例中触发
MyCustomWarning后,w是warnings.WarningMessage实例,其.category是警告类,.message是实际内容 - 若想匹配部分消息文本,可用
str(w.message)做in判断,避免硬编码全等 - 不加
record=True时,warnings被丢弃,列表为空,容易误判“没警告”
import warnings
<p>class MyCustomWarning(Warning):
pass</p><p>def risky_func():
warnings.warn("something's off", MyCustomWarning)</p><p>def test_risky_func_warns():
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") # 必须设为 always,否则可能被忽略
risky_func()
assert len(w) == 1
assert issubclass(w[0].category, MyCustomWarning)
assert "something's off" in str(w[0].message)
</p>
用 pytest.warns 断言更简洁
如果你用 pytest,pytest.warns 是专为测试警告设计的上下文管理器,语法更紧凑,失败时提示也更清晰。它内部也是调用 warnings.catch_warnings,但封装了常见逻辑。
关键点在于:它只捕获指定类型的警告(或其子类),且自动验证是否真的发生了——如果没触发,测试直接失败。
- 支持传入具体警告类,如
pytest.warns(MyCustomWarning) - 也可传字符串匹配消息,如
pytest.warns(MyCustomWarning, match="off") - 若警告被全局过滤(例如
filterwarnings = ignore在pyproject.toml中),pytest.warns会失效,需检查 pytest 配置 - 不支持同时断言多个不同警告类型,一次只能盯一种
import pytest <p>def test_with_pytest_warns(): with pytest.warns(MyCustomWarning, match="off"): risky_func() </p>
为什么 assertRaises 对 Warning 无效
unittest.TestCase.assertRaises 只捕获 Exception 及其子类,而 Warning 继承自 BaseException 但不是 Exception,所以它根本不会被 assertRaises 捕到——代码会静默通过,造成假阳性。
- 错误写法:
self.assertRaises(MyCustomWarning, risky_func)→ 永远不报错,但也没捕获到警告 - 本质原因:Python 把 Warning 设计为“非中断式提示”,和 Exception 的语义完全不同
- 如果真想让警告变成异常(仅调试用),可临时设
warnings.simplefilter("error"),但这会改变行为,不适合常规测试逻辑
自定义 Warning 类定义的常见疏漏
自定义警告类本身看似简单,但两个细节常被忽略,导致测试始终不匹配:
- 必须显式继承
Warning(或其子类如UserWarning),写成class MyWarning:或class MyWarning(Exception):都不行 - 模块路径影响匹配:如果测试里 import 的是
mylib.MyCustomWarning,但触发警告时用的是from mylib import MyCustomWarning再warn(..., MyCustomWarning),通常没问题;但如果跨文件重定义同名类(比如在测试文件里又写了一遍),issubclass会返回False - 警告类别在
w.category中是类对象,不是字符串,别用w.category == "MyCustomWarning"这种比较
测试里捕获 Warning 看似简单,但真正卡住人的往往是 warning 类型没对上、record=True 忘加、或者 pytest 配置把警告吞掉了。动手前先确认三件事:类继承链对不对、上下文里 filter 设没设对、测试运行时有没有外部 warning filter 干扰。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











