pytest源码解析(二)剖析 pytest 的核心组件

pytest 的架构是围绕一棵节点树 (Node Tree) 构建的。这棵树在收集阶段 (Collection) 被创建,代表了整个测试会话的结构。

1. Config 对象

作用 :这是 pytest 的全局上下文 和控制中心。它贯穿整个测试生命周期的始末,是所有配置信息、插件系统和共享数据的承载者。

关键属性和方法:

  • pluginmanager: 最重要的属性之一,是 pluggy.PluginManager 的实例。所有钩子的调用都通过它进行 (config.hook.x() 实际上是 config.pluginmanager.hook.x())。

  • option: 一个 argparse.Namespace 对象,存储了所有解析后的命令行参数(如 config.option.verbose, config.option.collectonly)。

  • _inicache: 存储从 pytest.ini、pyproject.toml 等配置文件解析出的内容。

  • rootpath: 项目根目录的路径。

  • stash: 一个字典类型的属性,用于在插件之间共享自定义数据,非常好用。例如,一个插件可以在 config.stash['my_cache'] = {} 中存储数据,另一个插件可以读取它。

  • hook: 钩子调用器。所有对 pytest 钩子的调用都通过 config.hook.hook_name(...) 完成。

生命周期 :在 pytest.main() 开始时创建,在整个会话结束时销毁。

2. Session 对象

作用 :根收集器 (Root Collector),代表一次完整的 pytest 测试会话。它是节点树的根,是所有其他节点(目录、文件、测试项)的父节点。

关键属性和方法:

  • items: 这是一个极其重要的列表。在收集阶段结束后,它包含了所有将要被执行的测试项 (Item) 的列表。pytest_collection_modifyitems 钩子操作的就是这个列表。

  • config: 指向全局的 Config 对象。

  • collect(): 方法。开始收集过程,从当前会话(通常是命令行指定的开始路径)向下收集,构建整个节点树。

继承关系 :Session -> Node -> Collector

3. Node 基类

作用 :所有其他组件(Session, Collector, Item)的抽象基类。它定义了节点树中每个节点都具有的基本属性和方法。

关键属性和方法:

  • name: 节点的短名称(如函数名 test_my_function)。

  • nodeid: 节点的唯一标识符,是它在节点树中的完整路径(如 test_file.py::TestClass::test_method)。这是报告和过滤中使用的关键ID。

  • parent: 指向父节点的引用(如一个 Module 节点的父节点是 Session 或 Directory)。

  • config: 指向全局的 Config 对象。

  • ihook: 一个便捷属性,等价于 self.config.hook,用于在当前节点调用钩子。

4. Collector 收集器

作用 :继承自 Node。负责收集子节点。它们构成了节点树的枝干。

类型:

  • Session: 根收集器。

  • Directory: 代表一个目录,收集其下的 __init__.py 文件(如果被视为包)和其他子目录或 Python 文件。

  • File: 通常具体化为 Module,代表一个 Python 文件(如 test_sample.py)。

关键方法:

  • collect(): 核心方法 。它被调用时,会返回一个子节点列表。这些子节点可以是更下层的 Collector(如 Module 收集 Class),也可以是最终的 Item(如 Module 收集 Function)。

    python

    伪代码概念

    def collect(self): collected_children = \[\] # ... 遍历自身范围内的对象(如模块的属性、类的属性)... for obj in found_objects: if is_test_class(obj): collected_children.append(Class.from_parent(parent=self, name=obj.name )) elif is_test_function(obj): collected_children.append(Function.from_parent(parent=self, name=obj.name)) return collected_children

5. Item 测试项

作用 :继承自 Node。代表一个最小的、可执行的测试单元 ,是节点树的叶子。最常见的两种是 Function(测试函数)和 UnitTestCase( unittest 风格的测试方法)。

关键方法:

  • runtest(): 核心方法 。当执行阶段到来时,调用此方法会真正运行测试 。对于 pytest.Function,它内部会调用 pytest_pyfunc_call 钩子,从而执行被 @pytest.fixture 装饰的依赖项和测试函数本身。

  • funcargs: 一个字典,在 setup 阶段后填充。键是测试函数参数的名称,值是对应的 fixture 返回值。item.runtest() 最终会执行 test_function(**item.funcargs)。

生命周期 :由 Collector 创建,由 pytest_runtest_protocol 执行。

6. Fixture 夹具系统

作用 :依赖注入系统,是 pytest 的核心功能。用于提供测试所需的数据、状态或环境设置/清理。

核心概念:

  • @pytest.fixture 装饰器 :用于声明一个函数是 fixture。可以指定 scope ("function", "class", "module", "session"), autouse, params(参数化)等。

  • request 对象 :一个具有魔力的上下文参数,会自动传递给每个 fixture 函数。它提供了关于"谁请求了这个 fixture"的信息。

    • request.node: 获取请求该 fixture 的 Item 或 Collector 节点。

    • request.function: 获取请求该 fixture 的测试函数(如果是函数级)。

    • request.cls: 获取请求该 fixture 的测试类。

    • request.module: 获取请求该 fixture 的测试模块。

    • request.config: 获取全局的 Config 对象。

    • request.addfinalizer(finalizer_func) 或 yield: 用于定义 teardown 清理逻辑。

  • pytest.fixture 内部工作原理:

    1. 发现 :当收集到一个 Item 时,pytest 会分析其参数列表。

    2. 匹配:对于每个参数名,去寻找同名的 fixture 函数。

    3. 排序:根据 fixture 的依赖关系和 scope 对它们进行排序。

    4. 缓存:为相同 scope 的请求缓存 fixture 实例。

    5. 执行 (Setup) :在测试运行的 setup 阶段,按顺序执行 fixture 函数,获取返回值并存入 item.funcargs。

    6. 注入 :在 call 阶段,将 funcargs 解包并传入测试函数。

    7. 清理 (Teardown) :在 teardown 阶段,执行通过 yield 或 addfinalizer 注册的清理函数。

相关推荐
databook19 小时前
使用 SciPy 库进行时间序列分析
python·数据分析·scipy
java资料站19 小时前
十一、评估测试
开发语言·人工智能·python
szial19 小时前
网络爬虫与 CDP 实战(二):浏览器能访问,Python 却被拒绝?拆开登录态与 CSRF
爬虫·python·csrf
青少儿编程课堂20 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
怕浪猫21 小时前
LLM 面试必问的 8 个问题,答不上来直接淘汰
人工智能·python·面试
谢亮_vipxieliang21 小时前
泛型擦除、通配符与 PECS 一次讲透
java·面试
老歌老听老掉牙21 小时前
从三维旋转的深度解析到Python实现:绕任意轴旋转的奥秘
python·三维旋转
打工仔折腾 AI1 天前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战
梦幻精灵_cq1 天前
sumdir——一个伪python.built-in的“术法”打造
python
weixin_440730501 天前
函数return、参数、参数组、递归函数、高阶函数等小结
开发语言·python