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.inipyproject.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 节点的父节点是 SessionDirectory)。

  • 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 的 ItemCollector 节点。

    • 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 阶段,执行通过 yieldaddfinalizer 注册的清理函数。

相关推荐
Lingxing6 小时前
事件流:深入理解事件冒泡、事件捕获与事件委托
前端·javascript·面试
前端小白19956 小时前
面试取经:浏览器篇-跨标签页通信
前端·面试·浏览器
寒水馨6 小时前
Windows 11 手动下载安装配置 uv、配置国内源
windows·python·国内源·uv·windows11
San306 小时前
JavaScript 入门精要:从变量到对象,构建稳固基础
javascript·面试·html
小猪乔治爱打球6 小时前
[Golang 修仙之路] 场景题:红包系统设计
后端·面试
花花无缺6 小时前
python自动化-pytest-标记
后端·python
小扳6 小时前
SpringBootWeb 篇-深入了解 ThreadLocal 存在内存泄漏问题
java·开发语言·spring boot·面试
THMAIL6 小时前
机器学习从入门到精通 - 循环神经网络(RNN)与LSTM:时序数据预测圣经
人工智能·python·rnn·算法·机器学习·逻辑回归·lstm
Source.Liu7 小时前
【Python自动化】 21.1 Pandas 读取 Excel 文件的完整指南
python·自动化·pandas