pytest 中 fixture 与类继承交互导致的问题

文章目录

      • 问题
      • 分析
        • 将属性绑定到 **类** 上
        • 使用 `scope='function'`
      • 解决方法
      • 为什么有两个不同的对象
        • 核心原因:fixture 的执行上下文
          • `scope='function'` 的情况
          • `scope='class'` 的情况
        • 为什么 pytest 要这样做?
        • 这是 pytest 的设计局限
      • 总结

本文探讨 Pytest 中 fixture 作用域与类继承的交互问题,介绍其执行顺序规则。
以 TestBase 类和 TestDerived 子类为例,指出当 init 函数的 fixture 作用域设为 class 时会出现子类无法使用 self.base 的情况,原因是 fixture 和测试方法在不同实例对象上执行。

在 pytest 中,通常情况下,fixture 的执行顺序主要由 scope 决定,但并非简单地"高级别先执行"。实际上,pytest 按照一种"由外到内"的方式执行不同 scope 的 fixture。

具体来说,fixture 执行顺序遵循以下规则:

  1. 首先按照 scope 从大到小的顺序执行:session > package > module > class > function
  2. 同一 scope 级别的 fixture 按照依赖关系执行:如果一个 fixture 依赖于另一个 fixture(通过参数引用),则先执行被依赖的 fixture
  3. 同一 scope 级别且无依赖关系的 fixture 按照它们在代码中的声明顺序执行

问题

下面是 pytest 中 fixture 作用域(scope)与 Python 类继承之间的交互方式导致的一个问题。

这个代码 TestBase 中,如果将 init 函数使用级别为 function 的scope 运行没问题,但是改成 class 级别后,子类中的方法就没使用 self.base 了。

import pytestclass TestBase:# @pytest.fixture(scope='class', autouse=True)  # 在 test_derived 中无法使用 self.base@pytest.fixture(scope='function', autouse=True)  # 可行def init(self):self.base = "base"class TestDerived(TestBase):def test_derived(self):assert self.base == "base"def test_derived2(self):assert self.base == "base"

分析

fixture 的触发机制问题

  • 对于 autouse=True 的 fixture,pytest 需要确定何时以及在哪个对象上执行它
  • 当 fixture 定义在类内部且使用 scope='class' 时,pytest 可能在处理 fixture 的执行上下文时出现了问题
将属性绑定到
import pytestclass TestBase:@pytest.fixture(scope='class', autouse=True)def init(self, request):print(f"Init fixture executing, self is: {self}")print(f"Request.cls is: {request.cls}")request.cls.base = "base"  # 确保设置在类上而不是实例上class TestDerived(TestBase):def test_derived(self):print(f"In test_derived, self is: {self}")print(f"self.__class__.base is: {getattr(self.__class__, 'base', 'NOT_FOUND')}")assert hasattr(self.__class__, 'base')assert self.__class__.base == "base"assert self.base == "base"

执行结果:

============================== 1 passed in 0.10s ==============================
Init fixture executing, self is: <src.practice_demo.te.TestDerived object at 0x0000026EA6DE32E0>
Request.cls is: <class 'src.practice_demo.te.TestDerived'>
PASSED                                  [100%]
In test_derived, self is: <src.practice_demo.te.TestDerived object at 0x0000026EA6E50760>
self.__class__.base is: base

可以发现:

  1. fixture 确实执行了Init fixture executing 说明 scope='class' 的 fixture 被正确触发
  2. 执行顺序没问题:fixture 先执行,然后才是测试方法
  3. 对象实例不同:注意两个关键的内存地址
    • fixture 中的 self: 0x0000026EA6DE32E0
    • 测试方法中的 self: 0x0000026EA6E50760

这就解释了为什么原始代码会失败,当使用类内部定义的 scope='class' fixture 时:

  • fixture 在一个 TestDerived 实例上执行(地址 2E0),设置了 self.base = "base"
  • 但测试方法 test_derived 在另一个不同的 TestDerived 实例上执行(地址 760
  • 这两个是完全不同的对象实例

解决:使用 request.cls.base = "base" 将属性设置在上而不是实例上,所以无论哪个实例都能访问到这个类属性。

使用 scope='function'

因为 function 级别的 fixture 会在每个测试方法的同一个实例上执行,所以 self.base 设置和访问都在同一个对象上。

class TestBase:@pytest.fixture(scope='function', autouse=True)def init(self):print(f"Init fixture executing, self is: {self}")self.base = 'base'class TestDerived(TestBase):def test_derived(self):print(f"In test_derived, self is: {self}")assert self.base == "base"

运行结果,地址相同:

============================== 1 passed in 0.10s ==============================
Init fixture executing, self is: <src.practice_demo.t.TestDerived object at 0x00000238DC562A90>
PASSED                                   [100%]
In test_derived, self is: <src.practice_demo.t.TestDerived object at 0x00000238DC562A90>

解决方法

使用类属性代替实例属性

如果 base 是类级别的共享状态,可以将其设置为类属性,而不是实例属性:

class TestBase:@pytest.fixture(scope='class', autouse=True)def init(self, request):request.cls.base = "base"  # 设置类属性class TestDerived(TestBase):def test_derived(self):assert self.base == "base"def test_derived2(self):assert self.base == "base"
  • 在这里,request.cls 指向当前测试类(TestDerived),通过 request.cls.base 设置类属性。
  • 这样,base 成为 TestDerived 的类属性,所有的实例都可以通过 self.base 访问。

或者,保持使用 function 级别,这确实更符合 Python 类实例的工作方式,因为每个测试方法实际上都是在一个新的类实例上运行的。

为什么有两个不同的对象

使用 scope='function'情况下,因为 function 级别的 fixture 会在每个测试方法的同一个实例上执行,所以 self.base 设置和访问都在同一个对象上。

但是,为啥 scope 为 class 时,会出现两个对象呢?

这与 pytest 的 fixture 执行机制Python 类方法调用机制 有关。

核心原因:fixture 的执行上下文

当在类内部定义 fixture 时,pytest 需要在某个对象实例上调用这个 fixture 方法。但是:

scope='function' 的情况
  1. pytest 为每个测试方法创建一个新的 TestDerived 实例
  2. 在这个实例上调用 init fixture
  3. 然后在同一个实例上调用测试方法
  4. 流程:创建实例 → 调用 fixture → 调用测试方法(都在同一个对象上)
scope='class' 的情况
  1. pytest 需要在类级别执行 fixture,但 fixture 仍然是一个实例方法
  2. pytest 创建一个 TestDerived 实例来调用 init fixture
  3. 但当执行具体的测试方法时,pytest 又创建了另一个新的实例
  4. 流程:创建实例A → 调用 fixture → 创建实例B → 调用测试方法
为什么 pytest 要这样做?

这实际上是 pytest 设计的一个特点(或者说是限制),打印对象 id :

import pytestclass TestBase:@pytest.fixture(scope='class', autouse=True)def init(self):print(f"\nFixture executing on instance: {id(self)}")self.base = "base"class TestDerived(TestBase):def test_derived(self):print(f"\nTest executing on instance: {id(self)}")print(f"hasattr(self, 'base'): {hasattr(self, 'base')}")if hasattr(self, 'base'):print(f"self.base: {self.base}")else:print("self.base does not exist")# assert hasattr(self, 'base')  # 这行会失败,先注释掉

运行这个代码:

Fixture executing on instance: 1523376596880
PASSED                                  [100%]
Test executing on instance: 1523376597312
hasattr(self, 'base'): False
self.base does not exist
这是 pytest 的设计局限

pytest 在处理类内部定义的 scope='class' fixture 时,无法很好地协调实例的生命周期。这就是为什么通常建议:

  1. 避免在类内部定义 class 级别的 fixture
  2. 将 class 级别的 fixture 定义在 conftest.py 中
  3. 或者使用 request.cls 来操作类属性而不是实例属性

所以看到的"两个对象"现象是 pytest 内部机制导致的,而不是 Python 或测试逻辑的问题。这也解释了为什么这种用法容易出现意想不到的行为。

总结

  • scope='function' 有效是因为 init 方法在每个测试函数运行时都会为当前实例设置 self.base
  • scope='class' 失败是因为 init 方法的 self 没有正确绑定到 TestDerived 的实例上,导致 self.base 未被设置。
  • 通过调整为类属性,可以解决这个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/83342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp+ts模拟popup弹出框(下拉框)

效果图&#xff08;未展开的样子&#xff09;&#xff1a; 效果图&#xff08;展开的样子&#xff09;&#xff1a; 子组件代码&#xff1a; <!--* Date: 2024-04-26 14:30:00* LastEditTime: 2025-05-29 09:01:06* Description: 技术服务 --> <template><view …

中小型企业大数据平台全栈搭建:Hive+HDFS+YARN+Hue+ZooKeeper+MySQL+Sqoop+Azkaban 保姆级配置指南

目录 背景‌一、环境规划与依赖准备‌1. 服务器规划(3节点集群)2. 系统与依赖‌3. Hadoop生态组件版本与下载路径4. 架构图二、Hadoop(HDFS+YARN)安装与配置‌1. 下载与解压(所有节点)2. HDFS高可用配置3. YARN资源配置‌4. 启动Hadoop集群三、MySQL安装与Hive元数据配置…

谷粒商城-分布式微服务项目-高级篇[三]

十五、商城业务-支付 15.1 支付宝支付 15.1.1 进入“蚂蚁金服开放平台” 支付宝开放 平台地址&#xff1a; 支付宝开放平台 15.1.2 下载支付宝官方 demo&#xff0c;进行配置和测试 开发者文档&#xff1a;支付宝开放平台文档中心 电脑网站支付文档&#xff1a;小程序文…

DeepSeek 赋能低空经济:无人机智能调度的破局之道

目录 一、引言二、DeepSeek 技术探秘2.1 DeepSeek 技术核心要点2.2 与传统技术对比优势 三、低空经济无人机调度挑战剖析3.1 飞行控制困境3.2 数据处理难题3.3 系统集成阻碍 四、DeepSeek 应用方案与成果4.1 智能调度与路径规划4.2 自主飞行与协同控制4.3 通信与数据链优化4.4 …

【Kubernetes】ubuntu20.04通过kubeadm + Docker安装k8s

Kubernetes v1.24集群安装配置步骤总结 一、环境准备 &#xff08;一&#xff09;系统要求 运行兼容deb/rpm的Linux操作系统&#xff08;如Ubuntu或CentOS&#xff09;的计算机&#xff0c;1台或多台。每台机器内存2GB以上&#xff0c;内存不足会限制应用运行。控制平面节点…

计算机视觉NeRF

NeRF与3DGS学习 NeRF计算机视觉的问题NeRF定义神经辐射场场景表示基于辐射场的体渲染分层采样优化神经辐射场 基础知识初始化SFM基础矩阵 & 本质矩阵 & 单应矩阵从已经估得的本质矩阵E&#xff0c;恢复出相机的运动R,tSVD 分解 NeRF NeRF资源 计算机视觉的问题 计算…

工业手持PDA终端,有哪些作用?

工业手持PDA终端&#xff08;便携式数据采集终端&#xff09;&#xff0c;是专为工业场景设计的智能化工具&#xff0c;拥有强大的数据采集和处理能力。通过内置的条码扫描功能&#xff0c;PDA能够快速准确地获取信息&#xff0c;避免了人工录入可能出现的错误&#xff0c;大大…

Spark-TTS: AI语音合成的“变声大师“

嘿&#xff0c;各位AI爱好者&#xff01;还记得那些机器人般毫无感情的合成语音吗&#xff1f;或者那些只能完全模仿但无法创造的语音克隆&#xff1f;今天我要介绍的Spark-TTS模型&#xff0c;可能会让这些问题成为历史。想象一下&#xff0c;你可以让AI不仅说出任何文字&…

C++链式调用与Builder模式

在C++中实现链式调用(如 a.b().c().d())的关键是让每个成员函数返回对象的引用(通常是 *this),从而允许连续调用其他成员函数。这种模式常见于方法链(Method Chaining)或流式接口(Fluent Interface)。下面是实现链式调用的具体方法和示例: 实现原理 返回对象引用:每…

SQL的查询优化

1. 查询优化器 1.1. SQL语句执行需要经历的环节 解析阶段&#xff1a;语法分析和语义检查&#xff0c;确保语句正确&#xff1b;优化阶段&#xff1a;通过优化器生成查询计划&#xff1b;执行阶段&#xff1a;由执行器根据查询计划实际执行操作。 1.2. 查询优化器 查询优化器…

结构型设计模式之桥接模式

文章目录 1. 桥接模式概述2. 模式结构3. 桥接模式的优缺点优点缺点 4. 桥接模式的应用场景5. C#代码示例5.1 简单示例 - 形状与颜色5.2 更复杂的示例 - 跨平台消息发送系统 6. 桥接模式与其他模式的比较7. 真实世界中的桥接模式应用7.1 数据库驱动7.2 UI框架中的渲染机制 8. 桥…

SolidWorks建模(U盘)- 多实体建模拆图案例

这个U盘模型并不是一个多装配体&#xff0c;它是一个多实体零件&#xff0c;它是在零件模式下创建的这些多实体的零部件。按右键解除爆炸就可以装配到一起&#xff0c;再按右键爆炸&#xff0c;就能按照之前移动的位置进行炸开 爆炸视图直接展示 模型案例和素材或取&#xff08…

计算机组成原理核心剖析:CPU、存储、I/O 与总线系统全解

引言 在当今数字化时代&#xff0c;计算机已经渗透到我们生活的方方面面&#xff0c;从智能手机到超级计算机&#xff0c;从智能家居到自动驾驶汽车。然而&#xff0c;你是否曾好奇过&#xff0c;这些功能强大的设备内部究竟是如何工作的&#xff1f;是什么让计算机能够执行各种…

SystemVerilog—Interface语法(二)

在SystemVerilog中&#xff0c;接口&#xff08;interface&#xff09;是一种封装信号集合、协议逻辑和通信行为的复合结构。其核心定义内容可分为以下十类&#xff1a; 1. 信号声明 基础信号&#xff1a;可定义逻辑&#xff08;logic&#xff09;、线网&#xff08;wire&…

DAY43打卡

浙大疏锦行 kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 fruit_cnn_project/ ├─ data/ # 存放数据集&#xff08;需手动创建&#xff0c;后续放入图片&#xff09; │ ├─ train/ …

[蓝桥杯C++ 2024 国 B ] 立定跳远(二分)

题目描述 在运动会上&#xff0c;小明从数轴的原点开始向正方向立定跳远。项目设置了 n n n 个检查点 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots , a_n a1​,a2​,⋯,an​ 且 a i ≥ a i − 1 > 0 a_i \ge a_{i−1} > 0 ai​≥ai−1​>0。小明必须先后跳跃到每个检查…

LINUX530 rsync定时同步 环境配置

rsync定时代码同步 环境配置 关闭防火墙 selinux systemctl stop firewalld systemctl disable firewalld setenforce 0 vim /etc/selinux/config SELINUXdisable设置主机名 hostnamectl set-hostname code hostnamectl set-hostname backup设置静态地址 cd /etc/sysconfi…

鸿蒙OSUniApp结合机器学习打造智能图像分类应用:HarmonyOS实践指南#三方框架 #Uniapp

UniApp结合机器学习打造智能图像分类应用&#xff1a;HarmonyOS实践指南 引言 在移动应用开发领域&#xff0c;图像分类是一个既经典又充满挑战的任务。随着机器学习技术的发展&#xff0c;我们现在可以在移动端实现高效的图像分类功能。本文将详细介绍如何使用UniApp结合Ten…

【Redis】大key问题详解

目录 1、什么是大key2、大key的危害【1】阻塞风险【2】网络阻塞【3】内存不均【4】持久化问题 3、如何发现大key【1】使用内置命令【2】使用memory命令&#xff08;Redis 4.0&#xff09;【3】使用scan命令【4】监控工具 4、解决方案【1】拆分大key【2】使用合适的数据结构【3】…

redis核心知识点

Redis是一种基于内存的数据库&#xff0c;对数据的读写操作都是在内存中完成&#xff0c;因此读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列、分布式锁等场景。 Redis 提供了多种数据类型来支持不同的业务场景&#xff0c;比如 String(字符串)、Hash(哈希)、 Lis…