【pytest高階】源碼的走讀方法及插件hook

一、pytest源碼走讀方法?

依賴庫認知篇 📦

這是理解 pytest 源碼的 “前菜”,先認識 3 個超重要的小伙伴:

  • iniconfig?📄:像個 “文件小管家”,專門負責讀取 ini 配置文件(比如 pytest 的配置),讓 pytest 知道你想怎么跑測試~
  • packaging?🎁:是個 “包信息解析大師”!能解析 Python 包的名字、版本、依賴這些細節,就像給包做 “身份認證”,pytest 里處理包相關邏輯少不了它~
  • pluggy?🔌: pytest 靈魂級 “插件小管家”!專門管理插件系統,讓 pytest 能靈活擴展功能,后續重點要掌握它喲~

簡單說:想讀 pytest 源碼,得先和這 3 個小伙伴混熟,它們是 “入門門票”🎫

?

pytest 源碼結構探索篇 🏠

找到 pytest 源碼目錄里的?src?文件夾,就像找到 “寶藏基地” 啦~里面:

  • _pytest?📂:藏著最核心的源碼邏輯,是 pytest “心臟” 所在~
  • pytest?📂:給普通用戶用的接口,像 “對外營業窗口”,你平時?import pytest?用的就是它封裝好的功能~
  • py.py?📄:像個 “小補丁”,讓 py 工具包能正常工作,默默當 “幕后英雄”~

在?_pytest?包里,還要 “篩一篩”:排除那些?_?開頭的(3 個包 + 44 個文件),剩下的才是咱重點看的核心源碼,像在 “淘金子”?

?

插件相關代碼占比篇 🔌

在 40 多個文件里,有 29 個帶著?def pytest_?這種插件相關的定義,說明 pytest 里超!級!多代碼和插件有關(大概占 3/4)!

這也側面說明:pluggy?為啥那么重要 —— pytest 靠它實現豐富的插件生態,讓 pytest 能 “七十二變”,適配各種測試需求 🦸

?

掌握 pluggy 用法(插件靈魂!) 🔮

插件是啥?用可愛話講:

  • 插件是軟件的 “魔法貼紙”?:給軟件貼一個,就能解鎖新功能(比如 pytest 裝個插件,能新增測試報告樣式、自定義斷言啥的)~
  • 插件能 “自由裝卸”🧩:想用就裝,不想用就卸,軟件本體功能不受影響,主打一個靈活!

對 pytest 來說:

  • 本體(軟件)提供基礎測試功能(跑用例、斷言這些)🏗?
  • 插件(魔法貼紙)負責擴展:想生成漂亮報告?裝報告插件!想改測試收集規則?裝自定義插件!讓 pytest 能 “變身” 滿足各種場景~

總結一下 “走讀 pytest 源碼” 學習路徑:

  1. 先搞定 Python 語言 + 標準庫基礎(打鐵還需自身硬 🔨)
  2. 吃透?iniconfig?packaging?pluggy?這 3 個依賴庫(和它們交朋友 🤝)
  3. 順著源碼結構(src?里的?_pytest?等),重點挖插件相關邏輯(畢竟占比超高 🔌)
  4. 深入掌握?pluggy?用法(它是 pytest 插件生態的 “發動機” 呀!)

這樣一步步走, pytest 源碼就從 “密密麻麻看不懂”,變成 “拆盲盒一樣有趣” 啦~ 后續再看代碼,就像 “認親戚”:哦~這里用了 pluggy 注冊插件、那里靠 packaging 解析版本… 慢慢就通啦 🚀

(簡單說就是:先熟依賴 → 看源碼結構 → 抓插件核心 → 掌握 pluggy → 打通 pytest 源碼任督二脈!)

二、pluggy 的用法~

可以把整個流程想象成給小狗?Dog?加裝 “說話后自動觸發插件” 的魔法系統🔮

?

前期準備:導入 pluggy 并創建核心對象 🛠?

import pluggy# 1. 創建插件管理器(起個名字,比如 'yifei')
pm = pluggy.PluginManager('yifei')  
# 2. 創建 hook 聲明標記(標記哪些是“可被插件擴展的鉤子”)
hookspec = pluggy.HookspecMarker('yifei')  
# 3. 創建 hook 實現標記(標記插件里的“鉤子實現函數”)
hookimpl = pluggy.HookimplMarker('yifei')  

這三步是基礎:

  • pm?是 “大管家”,負責管理所有插件、鉤子的注冊和調用~
  • hookspec?是 “鉤子聲明貼紙”:貼在函數上,說明 “這個函數是給插件擴展用的鉤子”~
  • hookimpl?是 “插件實現貼紙”:貼在插件函數上,說明 “這個函數是用來擴展鉤子的”~

?

定義主邏輯(小狗?Dog?類)🐕

class Dog:def __init__(self):# 把 Dog 類里的鉤子(@hookspec 標記的函數)注冊到插件管理器pm.add_hookspecs(Dog)  def say(self):print('woof! woof!')  # 小狗基礎叫聲# 調用 hook!小狗叫完后,觸發 say_after 鉤子,傳參數 arg1=1, arg2=2pm.hook.say_after(arg1=1, arg2=2)  # 用 @hookspec 標記:這是一個“鉤子”,插件可以擴展它!@hookspec  def say_after(self, arg1, arg2):"""say 方法調用后,自動調用本方法(插件會擴展這個邏輯)"""# 這里可以寫默認邏輯(也可以不寫,純靠插件擴展)pass

重點理解:

  • say()?是小狗的 “基礎動作”(叫一聲),但叫完后會觸發鉤子?say_after,讓插件有機會 “插話”~
  • @hookspec?標記的?say_after?是 “鉤子函數”:它定義了 “插件可以擴展的接口”(參數是?arg1, arg2),插件里的函數要和它參數匹配才能生效~

?

定義插件(給小狗加魔法的插件們)🧩

class Plugin_1:# 用 @hookimpl 標記:這是插件對 say_after 鉤子的實現!@hookimpl  def say_after(self, arg1, arg2):print(f'我是插件1,我收到了軟件的參數 {arg1}, {arg2}')class Plugin_2:@hookimpl  def say_after(self, arg1, arg2):print(f'我是插件2,我收到了軟件的參數 {arg1}, {arg2}')

插件的作用:
每個插件里的?say_after?函數,必須和鉤子函數(Dog?里的?say_after)參數一致(都有?arg1, arg2),這樣插件管理器才能識別~
插件里的邏輯,就是 “小狗叫完后,額外執行的代碼”(比如打印收到的參數)~

?

四、注冊插件 + 運行!🚀

# 注冊插件:把插件對象交給插件管理器“大管家”
pm.register(Plugin_1())  
pm.register(Plugin_2())  # 創建小狗實例,讓它叫一聲~
dog = Dog()
dog.say()

運行后,輸出會是:

woof! woof!
我是插件1,我收到了軟件的參數 1, 2
我是插件2,我收到了軟件的參數 1, 2

因為:

  1. 小狗?dog.say()?先執行基礎叫聲 →?woof! woof!
  2. 觸發?pm.hook.say_after(arg1=1, arg2=2)?→ 插件管理器找到所有注冊的插件(Plugin_1Plugin_2)里的?say_after?函數,依次執行它們的邏輯~

?

整體流程總結🎬

  1. 準備階段:創建插件管理器?pm、鉤子標記?hookspec/hookimpl?→ 像準備魔法道具~
  2. 定義主邏輯:小狗?Dog?有個動作?say(),動作后有個 “魔法鉤子”?say_after?→ 像給小狗裝了個 “觸發機關”~
  3. 寫插件Plugin_1Plugin_2?用?@hookimpl?標記,實現?say_after?邏輯 → 像給小狗準備 “魔法貼紙”,貼了就能擴展功能~
  4. 注冊 + 運行:把插件注冊到管理器,讓小狗叫 → 觸發鉤子,插件生效!小狗叫完,插件們 “插話” 打印內容~

這樣,整個 pluggy 插件系統的流程就跑通啦~ 核心就是:主程序定義鉤子(@hookspec),插件實現鉤子(@hookimpl),插件管理器(pm)負責協調調用~ 是不是像給程序裝了 “可擴展的魔法接口”,想加功能就寫插件,超靈活!?

三、pytest 里的 hook(鉤子)機制?

可以把 pytest 想象成一個 “愛交朋友的小機器人”,靠 hook 到處 “勾搭插件”,讓自己功能超豐富~ 🤖

?

先理解核心概念:hook 是怎么讓插件生效的?🔌

還記得之前的 pluggy 例子嗎?pm(插件管理器)調用 hook 時,所有注冊過的插件里的同名 hook 函數,都會被一起調用

  • 主程序(比如 pytest)說:“我現在要觸發?pytest_addhooks?這個 hook 啦~ 所有裝了這個 hook 的插件,快來執行!”
  • 插件們聽到召喚,就會 “同時響應”,一起執行自己的邏輯~

這樣,不用改主程序代碼,只要寫插件實現 hook,就能擴展主程序功能 → 這就是 “插件效果”!

?

pytest 的 hook 體系(pytest 如何用 hook 搞事情)🐍

pytest 內部重度依賴 pluggy 的 hook 機制,核心點:

1. pytest 的 hook 聲明在哪里?

pytest 把所有的?hook 聲明?放在?hookspec.py?文件里,大概有 52 個左右~ 每個 hook 長這樣:

@hookspec(historic=True)
def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:...

  • @hookspec?標記:說明這是一個 “給插件擴展用的鉤子”~
  • 函數名?pytest_addhooks?是 hook 的 “身份 ID”,插件里的函數要和它同名 + 參數匹配才能生效~

?

2. pytest 什么時候調用 hook?

pytest 對 hook 的調用,散落在各個源碼文件里~ 比如處理配置、收集測試用例、運行測試、生成報告… 每個關鍵步驟,pytest 都會喊一句:“hook.pytest_xxx?快來執行!”(比如?pytest_cmdline_main?pytest_collectstart?等等)

舉個栗子:
當你運行?pytest?命令時,pytest 內部會先處理配置(加載配置文件、解析命令行參數),然后調用?pytest_cmdline_main?這個 hook → 所有實現了這個 hook 的插件,都會被觸發!

?

3. 第一個被調用的 hook 是啥?

pytest 啟動后,第一個被調用的 hook?通常和 “初始化插件、加載配置” 有關(具體要看源碼,但核心邏輯是:先啟動插件管理器,再觸發 hook 讓插件參與初始化)~

不管第一個是誰,流程都是:
pytest 啟動 → 初始化插件管理器 → 調用 hook → 插件們響應 → 一起完成任務

?

pytest 內部的 hook 玩法:瘋狂調用,瘋狂擴展!🤯

pytest 內部可以說 “滿是 hook”,核心邏輯就是:

  1. 處理配置:加載配置文件、解析命令行參數(比如?pytest -v?里的?-v?參數)~
  2. 調用 hook:在關鍵節點(比如?pytest_cmdline_main)觸發 hook,讓插件有機會 “插手”~
  3. hook 套 hook:一個 hook 被調用時,可能又會觸發其他 hook → 形成 “鏈式反應”,讓插件能在多個環節擴展功能~

用可愛的話講:
pytest 就像一個 “愛熱鬧的派對主持人”,每進行到一個環節(比如 “開始收集測試用例”“生成測試報告”),就會大喊:“有沒有插件想表演節目?!” → 插件們(實現了對應 hook 的)就會沖出來表演~ 而且一個環節的表演,還能觸發下一個環節的表演邀請,派對越玩越嗨!🎉

?

為什么說 “代碼無法感知自己是主程序還是插件”?🤔

回到最開始的 pluggy 例子:

  • 主程序(比如?Dog?類)里的?say_after?是 hook 聲明~
  • 插件(Plugin_1?Plugin_2)里的?say_after?是 hook 實現~

但實際上,它們的代碼結構幾乎一樣(都是定義函數,用裝飾器標記)~ 運行時,插件管理器(pm)不管你是 “主程序的 hook” 還是 “插件的 hook”,只要注冊過、匹配上,就一起調用!

  • 對代碼來說,它不用關心自己屬于 “主程序” 還是 “插件” → 只要寫對 hook 函數,就能被調用~
  • 對插件來說,只要實現了 hook,就像 “自動成為主程序的一部分”,參與到流程里~

?

總結:pytest + hook + 插件 是怎么玩的?🎮

  1. pytest 是 “主程序框架”:定義了一堆 hook(在?hookspec.py),每個 hook 對應一個 “擴展點”~
  2. 插件是 “功能擴展包”:實現 pytest 的 hook(同名函數 +@hookimpl?標記),注冊到 pytest 里~
  3. hook 是 “連接器”:pytest 運行到關鍵步驟時,調用 hook → 插件們響應 → 一起完成功能~

簡單說: pytest 靠 hook 搭建了一個 “可無限擴展的舞臺”,想加功能就寫插件實現 hook,舞臺(pytest)和演員(插件)互不干擾,但又能完美配合~ 這就是 pytest 插件生態如此強大的原因!?

這樣,從 pluggy 的基礎用法,到 pytest 如何用 hook 玩出花,就串聯起來啦~ 核心就是:hook 讓主程序和插件解耦,想擴展功能只需要寫插件實現 hook,超靈活!?下次看 pytest 源碼,看到?hookspec.py?和各種?hook.pytest_xxx,就知道:“哦~ 這里又在喊插件來干活啦!” 🎉

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93315.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93315.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93315.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法訓練營day32 動態規劃理論基礎、509. 斐波那契數、70. 爬樓梯、746. 使用最小花費爬樓梯

今天開始動態規劃的部分! 其實說白了,動態規劃我感覺就是找類似遞歸的規律, 動態規劃理論基礎 動態規劃,英文:Dynamic Programming,簡稱DP,如果某一問題有很多重疊子問題,使用動態規…

基于神經網絡的手寫數字識別系統

基于神經網絡的手寫數字識別系統 結合模板匹配和神經網絡兩種方法進行手寫數字識別。這個系統包括圖像預處理、特征提取、神經網絡訓練和可視化分析。 %% 基于神經網絡的手寫數字識別系統%% 清理工作區 clear; clc; close all;%% 加載手寫數字數據集 % 使用MATLAB自帶的手寫數字…

機器學習?一文看懂這門熱門技術

🌟 什么是機器學習?一文看懂這門熱門技術在人工智能(AI)的大潮中,機器學習(Machine Learning, ML) 無疑是最耀眼的明星之一。它讓計算機具備了 “自我學習” 的能力,讓自動駕駛、智能…

Spring的初始化鉤子

1. PostConstruct JSR-250 標準注解(不是 Spring 獨有),用來標記 Bean 初始化完成后要執行的方法。會在 Bean 的構造方法執行完、依賴注入完成后執行。 使用實例: Component public class Demo {PostConstructpublic void init() …

【AI】Java生態對接大語言模型:主流框架深度解析

文章目錄1. Deep Java Library (DJL)2. LangChain4j(LLM)3. HuggingFace Inference API4. OpenAI Java Client技術對比矩陣架構設計建議在人工智能浪潮下,大語言模型(LLM)已成為技術核心。Java生態通過以下框架實現高效…

【06】C#入門到精通——C# 多個 .cs文件項目 同一項目下添加多個 .cs文件

文章目錄1 單個 .cs文件2 創建 多個 .cs文件2.1 添加Hero類2.1 添加ShowInfo類2.3 關于命名空間的引用2.4 所有.cs文件代碼3 test3項目文件下載1 單個 .cs文件 上一講中 描述游戲中英雄的角色 所有代碼在一個.cs文件中, 如果代碼很多,類很多&#xff0…

【MySQL基礎篇】:MySQL常用數據類型的選擇邏輯與正確使用

?感謝您閱讀本篇文章,文章內容是個人學習筆記的整理,如果哪里有誤的話還請您指正噢? ? 個人主頁:余輝zmh–CSDN博客 ? 文章所屬專欄:MySQL篇–CSDN博客 文章目錄數據類型1.數據類型分類2.數值類型int整形類型bit位類型float小…

三、搭建springCloudAlibaba2021.1版本分布式微服務-springcloud loadbalancer負載均衡

什么是負責均衡 Spring Cloud LoadBalancer是一個客戶端負載均衡器,類似于Ribbon,但是由于Ribbon已經進入維護模式,并且Ribbon 2并不與Ribbon 1相互兼容,所以Spring Cloud全家桶在Spring Cloud Commons項目中,添加了Sp…

Oracle不完全恢復實戰指南:從原理到操作詳解

核心提示:當誤刪表、日志損壞或控制文件丟失時,Oracle的不完全恢復是DBA最后的救命稻草。掌握關鍵恢復技術,可在數據災難中力挽狂瀾。一、不完全恢復核心概念 1. 核心特點 必須關閉數據庫:在MOUNT狀態下執行重做日志恢復權限要求&…

Linux之shell腳本篇(二)

一、shell編程之if語句引言Linux在shell編程中,通常都是以自上而下運行,但是為了提高其代碼嚴謹性,我們即引入了多條件 控制語句例如:if、for、while、case等語句,有時候針對條件我們還會結合正則表達式去運用。將這些…

如何在android framewrok dump camera data

實現dump 函數 實現1 void dumpBufferToFile(buffer_handle_t* buffer, int width, int height, int frameNum) {void* data NULL;GraphicBufferMapper::getInstance().lock(*buffer, GRALLOC_USAGE_SW_READ_OFTEN, Rect(width, height), &data);char filename[128];sprin…

機器學習中的可解釋性:深入理解SHAP值及其應用

機器學習可解釋性的重要性在人工智能技術快速發展的2025年,機器學習模型已經深度滲透到醫療診斷、金融風控、司法量刑等關鍵領域。然而,隨著模型復雜度的不斷提升,一個根本性矛盾日益凸顯:模型預測性能的提升往往以犧牲可解釋性為…

.NET9 使用 OData 協議項目實戰

.NET 中 ODate 協議介紹 OData(Open Data Protocol) 是一個開放的 Web 協議,用于查詢和更新數據。在 .NET 生態系統中,OData 被廣泛支持和使用。 主要特性 1. 統一的數據訪問方式 提供標準化的查詢語法支持 CRUD 操作支持元數據描述 2. 查詢能力 標…

Android 性能優化:提升應用啟動速度(GC抑制)

前言 在移動應用開發領域,啟動速度是用戶體驗的重要指標。對于Android應用而言,垃圾回收(Garbage Collection, GC)機制雖然是內存管理的核心,但在應用啟動期間頻繁觸發GC會顯著拖慢啟動速度。本文將深入探討如何通過GC…

做了一款小而美的本地校驗器

需求說明 前陣子收到一則讀者留言,指出:市面上AI核稿工具(ProWritingAid,WPS AI Spell Check,Writer,QuillBot,Grammarly)要么收費太高,要么讓人擔心文章泄露。 如下圖所…

uniapp + uview-plus 微信小程序二維碼生成和保存完整解決方案

uniapp + uview-plus 微信小程序二維碼生成和保存完整解決方案 ?? 項目背景 在開發微信小程序時,經常需要實現二維碼的生成和保存功能。本文檔提供了一個基于 uniapp + uview-plus 框架的完整解決方案,徹底解決了以下常見問題: ? Canvas API 兼容性問題 ? 微信小程序權…

Linux中應用程序的安裝于管理

Linux中應用程序的安裝于管理 一 . rpm安裝 1.掛載 光驅里面存放了很多rpm的軟件包 光驅在系統中使用時,需要掛載 mount /dev/cdrom /mnt/ cd /mnt[rootstw mnt]# ls CentOS_BuildTag GPL LiveOS RPM-GPG-KEY-CentOS-7 EFI images Packag…

mysql重置密碼

要區分 MySQL 是通過 systemd 還是傳統 service 管理,以及對應的密碼重置方案,可按以下步驟操作: 一、如何區分管理方式(systemd 還是傳統 service) 通過以下命令判斷系統默認的服務管理方式:檢查系統是否使…

C++ TAP(基于任務的異步編程模式)

🚀 C TAP(基于任務的異步編程模式)1. 引言:走進異步編程新時代(🚀) 在當今高性能計算領域,同步編程模型的局限性日益凸顯。傳統的回調地獄和線程管理復雜性促使微軟提出了基于任務的…

利用C++手撕棧與隊列的基本功能(四)

棧和隊列詳細教程可以觀看 https://www.bilibili.com/video/BV1nJ411V7bd?spm_id_from333.788.videopod.episodes&vd_sourcedaed5b8a51d3ab7eb209efa9d0ff9a34&p48棧和隊列概念 棧和隊列是限定插入和刪除只能在表的端點進行的線性表在裝電池、裝彈夾、拿放盤子時都會出…