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
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/83342.shtml
英文地址,請注明出處:http://en.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…