pytest 本身未变慢,而是Python 3.12的AST解析器对装饰器、类型注解及嵌套泛型测试函数校验更严格,导致采集阶段耗时增加;升级至pytest≥8.2.0可解决,因其适配新AST节点并跳过非测试节点深度分析。

不是 pytest 本身变慢了,而是 Python 3.12 的 AST 解析器对某些语法结构(尤其是带装饰器、类型注解或嵌套泛型的测试函数)做了更严格的校验,导致 pytest 在采集阶段(test collection)花更多时间构建 AST 节点树。这个问题在大型测试套件中尤其明显。
pytest 8.2+ 才真正适配 Python 3.12 的 AST 变更
Python 3.12 引入了新的 AST 节点类型(如 MatchClass、TypeVarType)和更细粒度的类型注解解析逻辑。旧版 pytest(≤ 8.1)仍沿用兼容 Python 3.11 的 AST 遍历路径,遇到新节点时会 fallback 到通用递归解析,效率下降显著。
- 如果你用的是
pytest,升级到pytest>=8.2.0是最直接有效的修复 -
pytest 8.2.0开始使用ast.unparse()替代部分手动遍历,并跳过非测试相关节点(如类型别名声明)的深度分析 - 注意:某些自定义插件(如
pytest-asyncio或老版本pytest-cov)若未同步更新,可能拖慢采集——建议检查pip list | grep pytest
type 语句和泛型别名会让采集卡顿
Python 3.12 新增的 type 语句(如 type StrList = list[str])和嵌套泛型(如 dict[str, list[tuple[int, bool]]])在模块顶层出现时,会被 pytest 的采集器误判为“可能含测试逻辑的可执行节点”,从而触发冗余的符号表扫描。
- 把这类类型定义移到
if False:块或typing.TYPE_CHECKING分支里,能跳过采集阶段解析 - 避免在
conftest.py中大量使用type别名——它会被所有子测试模块 import,放大开销 - 用
pytest --collect-only -v观察哪些文件耗时最长,重点关注含type/Generic/ParamSpec的模块
distutils 移除导致插件加载延迟
Python 3.12 彻底移除了 distutils,而部分老插件(如某些内部封装的 pytest-html 定制版)仍通过 distutils.version.LooseVersion 做版本判断。每次导入都触发 ImportError 回退 + 重试逻辑,累积成可观延迟。
- 运行
pytest --trace-config查看插件加载日志,搜索distutils或LooseVersion - 替换掉依赖
distutils的插件,或升级到其最新版(通常已迁移到packaging.version) - 临时规避:在
pyproject.toml的[tool.pytest.ini_options]下加disable_plugins = ["old-html-plugin"]
真正影响采集速度的从来不是测试数量,而是模块的 AST 复杂度和插件链的健壮性。Python 3.12 的变化让旧工具链的“容忍边界”暴露得更清楚——升级 pytest 版本只是起点,得顺藤摸瓜检查类型定义位置和插件兼容性。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











