pytest.raises(systemexit) 仅捕获直接抛出且未被拦截的 systemexit 异常,不捕获被 try/except 吞掉、子进程内退出、类型不匹配或间接调用路径中的异常。

pytest.raises(SystemExit) 能捕获 SystemExit,但必须满足几个硬性条件——不满足任意一条,它就“假装没看见”。
pytest.raises 只捕获直接抛出的异常,不穿透 try/except
pytest.raises 是个上下文管理器,它只监听自己 with 块内顶层抛出的异常。如果被测代码把 SystemExit 包在 try/except 里吞掉了(哪怕只是空 except:),或者做了 except SystemExit: pass,异常根本不会冒出来,pytest.raises 就收不到。
-
常见错误写法:
def bad_func(): try: sys.exit(1) # 这里被吞了 except SystemExit: pass # ← pytest.raises 永远等不到这个异常 正确做法:确保
sys.exit()或raise SystemExit是裸露执行的,没被任何中间层拦截。
子进程里的 SystemExit 父进程完全感知不到
如果你用 subprocess.run()、os.system() 或其他方式启动新进程,里面调用 sys.exit(),那只是子进程退出,父进程的 pytest.raises 完全无法捕获——它连异常的边都摸不着。
-
此时你应该检查的是子进程的
returncode:result = subprocess.run([sys.executable, "-c", "import sys; sys.exit(2)"]) assert result.returncode == 2
-
不要写:
with pytest.raises(SystemExit): # ❌ 这永远不触发 subprocess.run(...)
SystemExit.code 类型不一致导致断言失败
SystemExit(1) 的 .code 是整数;SystemExit("msg") 的 .code 是字符串。Python 3.10 对异常链和属性访问更严格,类型错一点,assert exc_info.value.code == 1 就会失败。
-
错误示例:
raise SystemExit("error") # .code 是 str # 后面却写 assert exc_info.value.code == 1 # int vs str → TypeError 或 AssertionError -
安全写法:
with pytest.raises(SystemExit) as exc_info: some_func() # 先确认类型再比较 assert isinstance(exc_info.value.code, (int, str)) assert exc_info.value.code in (1, "error")
pytest.raises 必须在测试函数内部直接调用,不能靠装饰器或间接路径
@pytest.mark.parametrize 或封装函数(如 def wrapper(): return func())容易掩盖真实执行路径。如果参数导致函数在 sys.exit() 前就抛了 TypeError 或 NameError,pytest.raises(SystemExit) 根本没机会运行。
- 最小可靠写法,拒绝任何包装:
def test_exits_with_code_1(): with pytest.raises(SystemExit) as exc_info: sys.exit(1) # 就这一行,干净、直白、可验证 assert exc_info.value.code == 1
真正容易被忽略的点是:SystemExit 不是“错误”,它是程序退出信号;pytest.raises 的行为完全取决于你是否让它站在异常抛出的正路上——偏一毫米,就失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











