资讯动态

深入 pytest:编写能“看到“全部收集测试项的 session 级 fixture(special.rst 实战与源码解析)

发布时间:2026/9/15 17:33:03 来源:尧图企业网站定制
深入 pytest编写能看到全部收集测试项的 session 级 fixturespecial.rst 实战与源码解析【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest导读本文围绕官方文档 special.rst 中的经典示例讲解如何在 pytest 中编写一个session作用域、autouse的 fixture让它在任何测试执行之前遍历整个测试会话已收集到的全部测试项并基于测试类结构触发自定义的callme类方法。通过阅读本文你将掌握Session.items与Node.getparent()这两个核心 API 的用法理解 pytest 收集树collection tree的组织方式并学会如何在真实项目中利用这一模式做全局预处理、类级初始化或钩子式扩展。1. 场景引入为什么需要一个看得见全部测试的 fixturepytest 的普通 fixture 只在单个测试项item的 setup 阶段生效它的视野被限定在当前测试所在的收集树路径内。但有一类需求天然要求全局视野在整个测试会话开始前对所有已收集的测试类执行一次统一的初始化动作根据测试类是否定义某个约定方法来决定是否调用它类似约定优于配置在测试真正运行之前完成一次性的资源分配、注册、预热或环境检查。pytest 给出的答案非常优雅把 fixture 的作用域提升到session。由于 session 级 fixture 在整个测试会话期间只 setup/teardown 一次而它所接收的request.node恰恰就是收集树的根节点——Session对象因此它天然拥有访问所有收集结果的能力。文档在 special.rst 中给出的示例正是这一思想的精炼演示一个名为callattr_ahead_of_alltests的 autouse session fixture遍历所有测试项检查它们所属的测试类是否定义了callme类方法若有则先调用它。2. 完整示例conftest.py 与测试模块2.1 conftest.pysession 级 autouse fixture将下面的内容写入项目根目录的conftest.py# content of conftest.py import pytest pytest.fixture(scopesession, autouseTrue) def callattr_ahead_of_alltests(request): print(callattr_ahead_of_alltests called) seen {None} session request.node for item in session.items: cls item.getparent(pytest.Class) if cls not in seen: if hasattr(cls.obj, callme): cls.obj.callme() seen.add(cls)逐行拆解这段代码代码片段作用pytest.fixture(scopesession, autouseTrue)声明 session 作用域、自动启用的 fixture。session表示整个测试会话只 setup 一次autouseTrue表示无需任何测试显式请求该 fixture 也会被自动注入request.node对于 session 级 fixturerequest.node即收集树的根节点——Session对象源码见 fixtures.py 中FixtureRequest的定义session.itemsSession.items是本次会话收集到的全部测试项Item列表源码见 main.pyitem.getparent(pytest.Class)沿收集树向上查找最近的Class收集器祖先返回该测试项所属的测试类节点详见下文第 3 节seen {None}用集合做去重保证每个测试类只触发一次callme。初值{None}是为了防御getparent返回None的情况例如模块级、函数级测试项不属于任何类避免cls.obj访问报错hasattr(cls.obj, callme)检查测试类对象上是否定义了callme方法约定式探测cls.obj.callme()调用类方法此时尚未运行任何测试2.2 test_module.py定义callme约定的测试类# content of test_module.py class TestHello: classmethod def callme(cls): print(callme called!) def test_method1(self): print(test_method1 called) def test_method2(self): print(test_method2 called) class TestOther: classmethod def callme(cls): print(callme other called) def test_other(self): print(test other) # works with unittest as well ... import unittest class SomeTest(unittest.TestCase): classmethod def callme(self): print(SomeTest callme called) def test_unit1(self): print(test_unit1 method called)注意文档特意强调这一机制对unittest.TestCase同样有效。因为unittest测试在 pytest 收集树中同样会生成Class收集器节点SomeTest本身就是unittest.TestCase的实例化类对象getparent(pytest.Class)依然能定位到它cls.obj.callme()照常可以调用其callme类方法。2.3 运行与输出关闭输出捕获-s运行$ pytest -q -s test_module.py callattr_ahead_of_alltests called callme called! callme other called SomeTest callme called test_method1 called .test_method2 called .test other .test_unit1 method called . 4 passed in 0.12s观察执行顺序这正是该模式的核心价值三个callme的调用全部发生在任何test_*方法运行之前且每个类只调用一次TestHello有两个测试方法也只调用一次。如果没有-s捕获机制会把 fixture 中的print输出吞掉因此文档示例特意使用-s来展示调用时序。3. 源码视角收集树、Session 与 getparent3.1 收集树Collector 与 Itempytest 把一次测试会话组织成一棵收集树collection tree。在 nodes.py 中Node是收集树所有组件的基类Collector是树中的内部节点Item是树的叶子节点。典型的层级关系是Session根见 src/_pytest/main.py#L592-L596 ├── Moduletest_module.py │ ├── ClassTestHello │ │ ├── Itemtest_method1 │ │ └── Itemtest_method2 │ ├── ClassTestOther │ │ └── Itemtest_other │ └── ClassSomeTest │ └── Itemtest_unit1Session被官方文档注释为the root of the collection tree它收集命令行给定的初始路径main.py。Class是测试方法所在的 Python 类收集器python.py其obj属性即类对象本身。3.2 Session.items本次会话的全部测试项Session.items在perform_collect中被填充为收集到的全部Item列表main.py并在pytest_collection_modifyitems之后定型。因此一旦收集阶段完成session.items就包含了本会话将运行的每一个测试项——这正是 session fixture 能够纵观全局的数据基础。从测试侧的验证看test_collection.py 中也大量使用了item.getparent(pytest.Class)来断言收集树的结构关系。3.3 getparent向上查找最近的祖先Node.getparent(cls)的语义是返回包括自身在内、离自己最近的那个是cls实例的祖先节点nodes.py。其实现遍历iter_parents()从自身开始沿parent指针一路向上直到根见 nodes.py命中即返回def getparent(self, cls): for node in self.iter_parents(): if isinstance(node, cls): return node return None因此对任意测试项调用item.getparent(pytest.Class)拿到的就是这个测试方法属于哪个测试类。若测试项是模块级函数不属于任何类则返回None此时seen {None}中的初始None就派上了用场——cls为None时既不会访问cls.objNone也已在seen中不会造成重复处理。4. 从原理到实战理解何时触发、触发几次4.1 执行时机为什么在一切测试之前session 级 fixture 的 setup 发生在整个测试会话的最早阶段早于任何测试项的pytest_runtest_setup/pytest_runtest_call。在 runner.py 中可以看到每个测试项的执行被拆分为 setup / call / teardown 三个阶段而 session 级 fixture 的初始化属于会话级 setup天然先于这一切。这也解释了输出中callattr_ahead_of_alltests called→ 三个callme→ 各测试方法的严格顺序。4.2 触发次数seen集合的去重价值seen {None}是这段示例中容易被忽略但极为关键的细节对TestHello的两个测试项test_method1、test_method2getparent(pytest.Class)返回同一个Class节点因此callme只触发一次初值None使得不属于任何测试类的裸函数测试项被安全跳过若去掉seen则每个测试方法都会重复触发一次callme破坏每个类只初始化一次的语义。4.3 与 unittest 的兼容性示例中SomeTest继承自unittest.TestCase其callme依然被调用。从收集机制看unittest.TestCase子类同样被包装为 pytest 的Class收集器cls.obj就是该测试类cls.obj.callme()等价于调用类上的callme类方法因此该模式对 xunit 风格测试天然生效。5. 应用延伸与注意事项5.1 可以扩展到哪些场景掌握session fixture session.items getparent这一组合后你可以举一反三按模块或包做全局前置改用item.getparent(pytest.Module)即可对每个测试模块执行一次约定方法收集信息统计在会话开始时统计session.items的数量、按 marker 分组、输出自定义收集报告环境预热与注册在任意测试运行前完成连接池预热、外部服务健康检查等一次性动作配合-s或日志输出可见约定式扩展钩子让测试类通过实现约定命名的方法如callme自动挂载到会话流程中形成轻量插件化机制。需要说明的是Session.items只有在收集完成后才具备完整内容若需要在收集过程中动态干预应优先考虑pytest_collection_modifyitems等官方收集钩子hookspec.py而本文模式更适合收集完毕、运行之前这一时机的全局动作。5.2 注意事项autouseTrue会让 fixture 对整个会话内所有测试生效若只想对特定目录生效可将该 fixture 放在对应目录的conftest.py中在 fixture 中对测试类对象做修改如调用callme属于运行期副作用应确保操作是幂等的、可重复的cls.obj.callme()使用的是类对象上的方法查找若希望同时支持实例方法语义可自行调整调用方式但示例中统一约定为classmethod。6. 总结本文示例虽然只有短短二十余行却浓缩了 pytest 三个关键机制fixture 作用域scopesession、自动启用autouseTrue以及收集树导航Session.itemsNode.getparent。理解它之后你就掌握了在测试真正开始之前纵览全局、按约定触发自定义逻辑的通用能力它既可以用于类级初始化也可以扩展到模块级、包级乃至整个会话级的自定义流程编排。延伸阅读完整的官方示例位于 doc/en/example/special.rst收集树相关的核心源码见 src/_pytest/nodes.py 与 src/_pytest/main.pyfixture 的注册与解析机制见 src/_pytest/fixtures.py相关测试用例可参考 testing/test_collection.py 与 testing/python/fixtures.py。【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价