pytest.raises(SystemExit) 仅捕获直接包裹的纯函数调用中抛出的 SystemExit,需确保无 try/except 吞掉、非子进程执行、参数类型与断言一致,且避免环境干扰。

直接原因:pytest 默认不捕获 SystemExit,除非你显式用 pytest.raises(SystemExit) 包裹调用,且该调用必须发生在测试函数内部、未被其他异常拦截的执行路径上。
pytest.raises(SystemExit) 不生效的常见场景
最常踩的坑是误以为只要函数里调用了 sys.exit(),就能在测试中“自然触发”捕获逻辑。实际并非如此:
-
pytest.raises只捕获它包裹的那一次调用抛出的异常;如果函数在try/except中自己吞掉了SystemExit(比如写了except SystemExit:却没 re-raise),pytest.raises就收不到任何东西 - 如果被测函数是通过子进程(如
subprocess.run)启动的,SystemExit发生在子进程中,父进程的pytest.raises完全感知不到——此时应检查returncode - 使用了
@pytest.mark.parametrize但参数导致函数提前抛出其他异常(如TypeError),SystemExit根本没机会执行,pytest.raises(SystemExit)会直接报“未捕获到异常”
Python 3.10 的兼容性细节
Python 3.10 对 SystemExit 本身没有行为变更,但它的异常链处理更严格。如果你在测试中用了 raise ... from e 或手动设置了 __cause__,而 pytest.raises 的匹配逻辑又依赖 excinfo.value.code,要注意:
-
SystemExit("msg")的.code是字符串,不是整数;SystemExit(1)的.code是整数。类型不一致会导致assert excinfo.value.code == 1失败 - 某些 pytest 插件(如
pytest-catchlog旧版)在 3.10 下可能干扰caplog对SystemExit相关日志的捕获,建议升级到pytest >= 7.0并用原生caplog
验证 SystemExit 的最小可靠写法
不要依赖装饰器或外部封装,用最直白的上下文管理器:
立即学习“Python免费学习笔记(深入)”;
def test_exits_with_code_2():
def bad_main():
import sys
sys.exit(2)
<pre class="brush:php;toolbar:false;">with pytest.raises(SystemExit) as excinfo:
bad_main()
assert excinfo.value.code == 2 # 注意:这里必须和 exit() 实际传入的类型一致
关键点:
- 被测函数必须是纯函数调用,不能带额外 try/catch
- 不要在测试函数里 import sys 后再调用
sys.exit()—— 这会让excinfo捕获的是测试函数自身的退出,而非被测逻辑 - 如果被测代码里用了
exit()(非sys.exit()),确保它没被重定义过(比如 IPython 环境下exit是个对象,不是函数)
真正容易被忽略的是:SystemExit 的 .code 类型完全取决于你传给 sys.exit() 的参数类型,字符串、整数、None 都合法,但断言时必须精确匹配。混用会导致测试在 Python 3.10 下静默失败,而不是抛错提示。


















