第 12 章 · 插件体系与常用插件
本章目标:理解 pytest"装了就生效"的插件加载机制(entry-point、conftest.py、
-p三条通道),学会排查与禁用插件,并盘点测试工程中最值得安装的几个插件。
12.1 插件为什么"装了就生效"
pytest 的可扩展性建立在 pluggy 插件框架之上。任何 Python 包只要在打包元数据里注册了 pytest11 entry point,pip 安装后 pytest 启动时就会自动发现并加载它:
pip install pytest-timeout # 无需任何配置,立即生效验证方式是查看扩展的测试头信息:
$ pytest --trace-config
...
plugins: cov-7.x, xdist-3.x, timeout-2.x除了 entry-point 自动加载,还有两条显式通道:
pytest_plugins变量:在根conftest.py中声明要加载的插件模块;python# content of conftest.py(仅限 root conftest,非根 conftest 使用已弃用) pytest_plugins = ("myapp.testsupport.myplugin",)-p NAME命令行选项:启动期按名字加载插件模块或 entry point。
第 2 条通道加载的就是普通 Python 模块,这意味着你可以在仓库内维护私有插件。一个最小可用的本地插件长这样:
# content of myapp/testsupport/mini_plugin.py
def pytest_addoption(parser):
# 注册自定义命令行选项
parser.addoption("--env", default="test", help="目标环境")
def pytest_configure(config):
# 启动期读取配置并打标
config.option.env = config.getoption("--env")# content of conftest.py —— 用 -p 加载上面的本地插件
pytest_plugins = ("myapp.testsupport.mini_plugin",)之后运行 pytest -p myapp.testsupport.mini_plugin --env=staging 即可让整个套件感知目标环境。
名字冲突提醒
pytest_plugins 这个变量名被 pytest 保留,不要用它命名自己的插件模块。
12.2 插件的三个层级与禁用方法
pytest 把插件分为三层:内置插件(随主包发布,如 cacheprovider)、外部插件(entry-point 自动发现的 pip 包)、本地插件(各目录的 conftest.py)。排查环境问题时,先分清"哪个层级的谁"提供了某个行为。
禁用插件的几种姿势:
pytest -p no:cacheprovider # 本次运行禁用内置缓存插件
pytest --trace-config # 先查名字
pytest --disable-plugin-autoload # 8.4+ 关闭全部自动加载
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 PYTEST_PLUGINS=cov,xdist pytest # 只加载白名单要把某项禁用固化为项目约定,写进配置文件的 addopts 即可:
# content of pyproject.toml
[tool.pytest.ini_options]
addopts = ["-p", "no:cacheprovider"] # 项目内永久关闭 cache 插件显式控制时避免重复注册
官方文档特别提示:使用 --disable-plugin-autoload 时,不要再通过多种机制同时指定同一插件(如既 -p xdist 又 PYTEST_PLUGINS=xdist),重复注册会导致错误。
12.3 常用插件盘点
结合本课程后续章节,重点认识这些官方文档点名的高人气插件:
| 插件 | 一句话定位 | 本课程出现位置 |
|---|---|---|
pytest-cov | 覆盖率统计,兼容分布式运行 | 第 13 章 |
pytest-xdist | 多 CPU/多主机并行执行 | 第 14 章 |
pytest-timeout | 按标记或全局设置给测试加超时,防止卡死 | 本章实战 |
pytest-rerunfailures | 失败自动重跑,治理 flaky 测试 | 第 20 章 |
pytest-instafail | 失败即时报告,不等整轮跑完 | — |
pytest-django / pytest-bdd | 框架级/BDD 级集成 | — |
以 pytest-timeout 为例感受"零配置可用 + 配置可调"的插件体验:
# content of test_timeout.py
import time
import pytest
@pytest.mark.timeout(0.5) # 单测级别:该测试超过 0.5 秒判失败
def test_slow_query():
time.sleep(5)
assert True# content of pyproject.toml —— 全局默认 10 秒,防止 CI 卡死
[tool.pytest.ini_options]
addopts = ["--timeout=10"]完整插件目录可在 PyPI 以 pytest- 前缀检索;官方还维护了按 pytest/Python 版本测试状态的插件列表页面。
另一个高频场景是给慢测试自动打标记——用几行 hook 代码就能实现"类插件"的行为,这也是第 16 章自定义插件的预告:
# content of conftest.py —— 收集期为 integration 模块的测试自动加 slow 标记
import pytest
def pytest_collection_modifyitems(config, items):
for item in items:
if "slow" in item.keywords: # 已显式标记的不重复处理
continue
if getattr(item.function, "__module__", "").endswith("integration"):
item.add_marker(pytest.mark.slow)12.4 选型建议:少即是多
初学者常见误区是一次装十几个插件。每个插件都会改变收集、执行或报告行为——插件越多,"我的 pytest 和你行为不一样"式的环境问题越难排查。建议:
- 从零插件起步,遇到真实痛点再引入对应插件;
- 用
required_plugins(第 11 章)锁定团队必需插件及最低版本; - CI 与本地保持同一份
pyproject.toml配置,差异只通过PYTEST_ADDOPTS注入。
12.5 本章小结
- entry-point(
pytest11)让插件"装了就生效";pytest_plugins变量与-p选项提供显式加载通道; --trace-config查看激活的插件,-p no:NAME单次禁用,--disable-plugin-autoload关闭自动加载(8.4+);pytest-cov/xdist/timeout/rerunfailures是生产项目最常用的四件套;- 插件宁缺毋滥,用
required_plugins固化依赖。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. pip install pytest-timeout 后无需任何配置即被 pytest 加载,靠的机制是?
2. 想看当前运行激活了哪些插件及其来源,应执行?
3. 本次运行临时禁用内置 cache 插件,正确的命令是?
4. 关于 pytest_plugins 变量,说法正确的是?
🛠️ 动手实践
- 安装
pytest-timeout,给一个time.sleep(10)的测试打上@pytest.mark.timeout(1)标记,观察失败输出,再用-p no:timeout对比禁用后的行为。 - 运行
pytest --trace-config记录你的环境中激活的全部插件;尝试用-p no:cacheprovider运行两次并检查.pytest_cache是否不再更新。 - 在根
conftest.py里用pytest_plugins声明一个你自己写的最小插件模块(哪怕只有一个空 hook),验证它能被加载。
下一章把最重量级的插件 pytest-cov 讲透:覆盖率报告怎么读、分支覆盖率是什么、如何在 CI 上设质量门禁:第 13 章。