昨天跟大家聊了 pytest 自动化框架的基础用法(用例发现、fixture、参数化、简单插件),不少同学私信说:面试总被问 “pytest 底层原理”“插件怎么写”“hook 是什么”**,只会用不够,得懂点内核与扩展。
今天就把pytest 底层执行流程 + 钩子机制 + 自定义插件开发 + fixture 底层原理一次性讲透,面试 / 实际造轮子都能用得上。
一、先看全貌:pytest 底层架构
pytest 核心是:轻量内核 + 插件化(Hook 驱动)+ 节点树用例管理 + Fixture 依赖注入。
- 内核只负责生命周期调度:启动 → 收集 → 执行 → 报告 → 结束
- 所有 “功能” 都是插件:内置插件 + 第三方插件 + 你写的 conftest / 自定义插件
- 用例被组织成Node 树:Session → Package → Module → Class → Function
- Fixture 本质是带作用域的依赖注入 + 资源生命周期管理
二、pytest 完整生命周期(面试必背)
敲下 pytest,底层跑的是这一条链:
1.入口:pytest.main() → 创建 Config + PluginManager
2.初始化:_prepareconfig → 加载插件、解析配置 / 命令行
3.收集用例:
- pytest_collection → 遍历目录
- 识别 test_.py / Test类 / test_* 函数
4.执行循环:pytest_runtestloop → 逐个执行用例
每个用例三步:
- Setup(fixture 执行)
- Call(跑 test 函数)
- Teardown(fixture 清理)
5.报告:pytest_runtest_makereport → 生成结果
6.结束:pytest_sessionfinish → 汇总、退出码
一句话总结:pytest 就是靠一堆 Hook 串起来的生命周期。
三、核心扩展:Hook(钩子)是什么?
1. 通俗理解
Hook =框架留好的 “回调口子”,你在这些口子上挂自己函数,框架跑到对应阶段就自动调用你的代码。
比如:
- 收集用例前:你可以动态加用例 / 过滤用例
- 每个用例执行前:你可以打日志 / 初始化环境
- 执行后:你可以自定义报告 / 发通知
2. 常用内置 Hook(高频)
# conftest.py 里直接写,不需要装饰器defpytest_addoption(parser):"""加自定义命令行参数"""parser.addoption("--env",default="test",help="指定环境")defpytest_collection_modifyitems(config,items):"""收集完用例后:过滤/排序/打标记"""foriteminitems:if"slow"initem.name:item.add_marker(pytest.mark.slow)defpytest_runtest_setup(item):"""每个用例执行前"""print(f" 准备执行:{item.nodeid}")defpytest_runtest_teardown(item):"""每个用例执行后"""print(f" 执行完成:{item.nodeid}")defpytest_sessionfinish(session,exitstatus):"""整个测试结束后:汇总报告、发钉钉/邮件"""print(f" 用例总数:{session.testscollected}")3. Hook 底层原理
pytest 基于 pluggy 库做插件管理:
- 所有 hook 按 pytest_* 命名
- 插件启动时注册 hook 实现
- 框架通过 config.hook.xxx() 触发所有注册函数(1:N 调用)
四、Fixture 底层原理(面试常问:为什么比 setup/teardown 强?)
1. Fixture 本质
不是 “函数”,是带作用域的资源工厂 + 依赖注入容器
yield 前 = setup,后 = teardown
作用域控制生命周期:function(默认)/class/module/session
底层执行流程(很关键)
1.收集用例时:分析用例依赖的 fixture(递归解析)
2.按依赖顺序 + 作用域实例化 fixture
3.注入到 test 函数
4.用例结束:反向销毁(作用域长的最后销毁)
为什么比 unittest 灵活?
✅ 依赖注入:用例只管声明要什么,不管怎么创建
✅ 作用域精细:数据库连接用 session,临时文件用 function
✅ 可叠加 / 可覆盖:局部 conftest 覆盖全局,就近原则
五、实战:写一个自定义 pytest 插件(可直接用)
场景
想实现:
命令行加 --env
所有用例自动带环境标记
执行前后打日志
1. 目录结构
tests/├── conftest.py# 本地插件/hook└── test_demo.py2.conftest.py(完整代码)
importpytest# 1. 加命令行参数defpytest_addoption(parser):parser.addoption("--env",default="test",choices=["dev","test","prod"],help="运行环境")# 2. 全局 fixture:获取环境@pytest.fixture(scope="session")defenv(request):returnrequest.config.getoption("--env")# 3. 收集用例后:自动加标记defpytest_collection_modifyitems(config,items):env=config.getoption("--env")foriteminitems:item.add_marker(getattr(pytest.mark,env))# 4. 每个用例前后日志defpytest_runtest_setup(item):print(f"\n[SETUP] 开始:{item.nodeid}")defpytest_runtest_teardown(item):print(f"[TEARDOWN] 结束:{item.nodeid}")3. 测试用例 test_demo.py
deftest_sample(env):print(f"当前环境:{env}")assert1==14. 运行
pytest tests/-v--env=dev效果:自动带环境标记、前后日志、全局环境 fixture。
六、面试高频问题(直接背)
pytest 底层架构是什么?
插件化 + Hook 驱动 + 节点树 + Fixture 依赖注入,内核轻量,功能靠插件。
fixture 作用域有哪些?执行顺序?
function/class/module/session;按依赖顺序创建,反向销毁。
hook 和 fixture 区别?
hook 改框架流程(收集 / 执行 / 报告);fixture 管测试资源(前置 / 后置 / 数据)。
如何写一个 pytest 插件?
实现 hook 函数 → 注册 → 打包(或直接放 conftest)
七、总结
pytest 强大不是因为语法多复杂,而是:
内核极简:只做调度
扩展极强:Hook + 插件体系
资源管理优雅:Fixture 依赖注入 + 作用域
会用只是入门,懂底层、会写插件、能定制流程,才是测开 / 高级测试的核心竞争力。