pytest在python 3.12下启动慢的主因是ast解析更严格、插件加载异常及顶层type语句触发冗余扫描;升级至pytest≥8.2.0可解决,因其适配新ast节点并跳过非测试节点深度分析。

Pytest 在 Python 3.12 下启动慢,90% 的情况不是你项目写得差,而是 AST 解析、插件加载或类型定义触发了新版本的严格校验机制。升级到 pytest>=8.2.0 是底线,否则光采集阶段就可能卡住几秒甚至几十秒。
为什么 pytest --collect-only 耗时特别长?
Python 3.12 引入了更细粒度的 AST 节点(如 TypeVarType、MatchClass),旧版 pytest(≤8.1)会 fallback 到通用递归遍历,遇到 type 语句或嵌套泛型(如 dict[str, list[tuple[int, bool]]])就反复扫描符号表。
- 用
pytest --collect-only -v观察各文件耗时,重点关注含type、Generic、ParamSpec的模块 - 把顶层类型别名挪进
if False:或typing.TYPE_CHECKING分支里,能跳过采集 - 避免在
conftest.py中大量定义type别名——它会被所有测试模块 import,放大开销
distutils 移除导致插件加载卡顿
Python 3.12 彻底删掉 distutils,但某些老插件(比如内部魔改版 pytest-html)还在用 distutils.version.LooseVersion 做版本判断,每次导入都触发 ImportError 回退 + 重试,累积成可观延迟。
- 运行
pytest --trace-config,搜distutils或LooseVersion确认问题插件 - 替换或升级插件(新版基本已迁移到
packaging.version) - 临时规避:在
pyproject.toml的[tool.pytest.ini_options]下加disable_plugins = ["old-html-plugin"]
如何让 pytest -n auto 真正快起来?
pytest-xdist 并行本身不慢,但默认会把每个 worker 都重新执行一遍采集逻辑,如果采集本身就慢,多进程反而加重负担。
- 确保已升级
pytest>=8.2.0,否则-n只是把慢操作复制 N 份 - 用
--dist=loadgroup替代默认的--dist=load,按测试类/模块分组分发,减少跨进程 fixture 初始化次数 - 对 I/O 密集型测试(如 API 调用),加
--max-worker-restart=3防止某个 worker 卡死后拖垮整条流水线
fixture 作用域滥用会悄悄拖慢启动
session/module 级 fixture 如果只被少数几个测试用到,却在采集阶段就被提前初始化,等于白占内存、白耗时间——尤其当它内部有数据库连接、HTTP 客户端等重型资源时。
- 用
@pytest.fixture(scope="function")显式声明,别依赖默认值 - 检查
conftest.py里有没有带副作用的全局 fixture(比如自动创建临时目录、预热缓存) - 对高开销资源,考虑用 lazy-init 模式:
if not hasattr(self, '_db_conn'):再初始化
真正卡住人的从来不是测试数量,而是模块的 AST 复杂度和插件链的健壮性。Python 3.12 的改动藏得深,一个没声明 @pytest.mark.asyncio 的 async 测试函数,或一行 type StrList = list[str],都可能让采集阶段多花 3 秒——而这 3 秒在 CI 里会被放大成分钟级等待。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











