注册基础 pytest 插件的关键是让 pytest 正确发现并加载该模块:最轻量方式是通过 pyproject.toml 声明入口点 [project.entry-points."pytest11"],插件名不能含下划线,模块需可导入且非 conftest.py;本地调试可用 pythonpath + --trace-config 验证识别。

如何注册一个基础 pytest 插件模块
pytest 插件本质是 Python 模块,只要被正确发现并加载即可运行。关键不是“写插件”,而是“让 pytest 认出它是插件”。最轻量的方式是把模块放在项目根目录或 sys.path 中,并在 pyproject.toml 或 setup.py 里声明入口点。
推荐用 pyproject.toml 声明(无需打包):
[project.entry-points."pytest11"] myplugin = "myplugin:pytest_plugin"
其中 myplugin 是插件名(任意合法标识符),myplugin:pytest_plugin 表示模块 myplugin.py 中的全局变量或对象 pytest_plugin —— 它可以是 None,只要模块能被 import 就行;更常见的是直接导出 hook 实现函数。
- 模块文件名不能是
conftest.py,否则会被 pytest 当作配置文件提前加载,hook 可能失效 - 如果只在本地开发调试,可临时用
export PYTHONPATH=$(pwd)+pytest --trace-config确认插件是否被识别 - 插件名(entry-point key)不能含下划线,否则 pytest 会静默忽略
如何实现常用 hook:pytest_runtest_makereport
这个 hook 能拿到每个测试用例执行后的原始报告对象,是做失败重试、截图、日志增强的最常用入口。注意它不修改执行流程,只提供观察和补充能力。
典型用法是在插件模块中定义函数:
def pytest_runtest_makereport(item, call):
if call.when == "call" and call.excinfo is not None:
# 测试主体执行失败
item._failed_reason = str(call.excinfo.value)
之后可在 pytest_runtest_logreport 或 fixture 中读取 item._failed_reason。
-
call.when可能是"setup"、"call"、"teardown",别在"setup"阶段误判失败 - 不要在该 hook 里 raise 异常,会导致 pytest 内部状态错乱
- 若需修改报告内容(如添加 extra info),应返回新
TestReport实例,但需小心兼容 pytest 版本(v7+ 的TestReport构造参数有变)
如何安全地向 fixture 注入动态行为
直接 patch pytest.fixture 装饰器不可靠。正确方式是使用 pytest_fixture_setup hook,在 fixture 实例化时注入逻辑:
def pytest_fixture_setup(fixturedef, request):
if fixturedef.argname == "browser":
# 给 browser fixture 加个超时控制
original = fixturedef.func
def wrapped(*args, **kwargs):
kwargs.setdefault("timeout", 30)
return original(*args, **kwargs)
fixturedef.func = wrapped
这种方式绕过装饰器链,直接干预 fixture 函数本身。
- 修改
fixturedef.func是高危操作,务必保留原函数引用,避免多插件冲突 - 仅对明确命名的 fixture 操作,不要通配匹配,否则可能破坏 pytest 内置 fixture(如
tmp_path) - 若需在 fixture 执行前后加逻辑,优先考虑
pytest_fixture_post_finalizer和pytest_fixture_pre_finalizer,它们更安全
为什么 pytest_configure 不该做耗时初始化
pytest_configure 在 pytest 解析完所有参数、收集完测试项之前就调用,看似是“最早入口”,但此时 config 对象尚未完全就绪,且该 hook 会被每个 pytest 进程调用一次 —— 在 --workers 或 --dist 场景下,重复初始化会导致资源争抢或端口占用冲突。
- 数据库连接、WebDriver 启动、日志文件打开等操作,应延迟到
pytest_sessionstart或首个 fixture 中进行 - 若必须预加载配置,只读取
config.option或config.getini,避免调用config.pluginmanager.register等副作用方法 - 调试时可用
print(f"[{os.getpid()}] configure")验证是否被多次触发
插件真正的复杂点不在 hook 写法,而在于生命周期判断:哪个时刻对象可用、哪个时刻已过期、哪个操作会跨进程污染。没理清这点,插件在 CI 或分布式环境下大概率表现异常。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











