pytest.raises 是上下文管理器,需用 with 语法断言异常;可赋值捕获异常实例检查 message,推荐用 in 或 match 参数匹配错误信息;注意异常未抛出、被吞或异步场景导致失效。

如何用 pytest.raises 捕获并断言特定异常类型
直接用 pytest.raises 包裹可能出错的代码块,就能验证是否抛出了预期异常类型。它不是装饰器,也不是上下文管理器的替代品——它本身就是个上下文管理器,必须配合 with 使用。
常见错误是写成 pytest.raises(ValueError) 却没加 with,结果测试永远通过(因为没执行任何断言);或者误以为它能“捕获后继续运行”,其实它只负责验证抛出行为,不吞异常。
- 正确写法:
with pytest.raises(ValueError): int("abc") - 如果想进一步检查异常实例,可以赋值给变量:
with pytest.raises(ValueError) as exc_info: int("abc") assert "invalid literal" in str(exc_info.value) - 不支持用
pytest.raises测试未抛异常的场景——它会直接失败,没法“反向断言”
如何断言异常的具体错误消息(message)
异常类型对了还不够,业务逻辑常依赖错误信息做判断(比如前端提示、重试策略)。exc_info.value 是原始异常对象,str(exc_info.value) 就是它的字符串表示,可直接用于 assert。
注意:不同 Python 版本对异常消息的格式化略有差异(比如空格、括号),建议用 in 而非全等匹配,避免因细微格式变化导致测试脆弱。
- 推荐:
assert "expected" in str(exc_info.value) - 不推荐:
assert str(exc_info.value) == "expected"(易因空格或换行失败) - 若需正则匹配,可用
re.search(r"pattern", str(exc_info.value)),但多数情况没必要
pytest.raises 的 match 参数能否替代手动检查 message?
能,而且更简洁。参数 match 接收正则字符串,pytest 会自动编译并匹配 str(exc_info.value)。它比手写 assert ... in ... 更严格(要求完整匹配整个 message),也更安全(自动处理转义)。
但要注意:它默认使用 re.search 行为,不是 re.fullmatch,所以 match="not found" 会匹配 "KeyError: 'not found'" 中的子串,而非整条消息。
- 简单关键词匹配:
with pytest.raises(KeyError, match="user_id"): get_user(None) - 需要锚定开头或结尾时,显式加
^或$:match=r"^Invalid.*format$" -
match不支持模糊匹配(如忽略大小写),得写match=r"(?i)invalid"
为什么有时 pytest.raises 不生效?常见陷阱
最典型的是异常在测试函数外被吞掉,比如被 try-except 捕获却没 re-raise,或者被 logging.error 吞掉后没抛出。这时 pytest.raises 看不到异常,直接报 “DID NOT RAISE” 错误。
另一个容易忽略的点:异步函数(async def)不能直接用 pytest.raises 包裹,必须 await 或用 pytest-asyncio 配合 await pytest.raises(...)(实际不支持,得改用 async with + 自定义逻辑)。
- 检查被测函数是否真抛异常:在测试里先单独调用,看终端是否打印 traceback
- 确认没被上层 try/except 拦截,尤其是 mock 或装饰器里静默处理了异常
- 对异步代码,改用
with pytest.raises(...): await async_func()(前提是测试函数本身是async def,且 pytest 配置了 asyncio 插件)
异常断言看着简单,但 message 格式、异常传播路径、异步上下文这三块最容易漏查。写完记得删掉调试 print,再跑一遍——有时候多一个空格,CI 就挂。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











