首頁 > 後端開發 > Python教學 > 在 Python 測試中避免「模擬地獄」的實用技巧

在 Python 測試中避免「模擬地獄」的實用技巧

Patricia Arquette
發布: 2025-01-20 18:21:12
原創
862 人瀏覽過

ractical Hacks for Avoiding “Mocking Hell” in Python Testing

在 Python 測試中逃離「模擬地獄」的七種經過驗證的技術

簡介

對 Python 的 unittest.mock 函式庫感到沮喪? 您的測試是否仍然進行真正的網路呼叫或拋出令人困惑的 AttributeError 訊息?這個常見問題通常被稱為“模擬地獄”,會導致測試緩慢、不可靠且難以維護。這篇文章解釋了為什麼模擬對於快速、可靠的測試至關重要,並提供了七種實用策略來有效地修補、模擬和隔離依賴項,確保「模擬健康」。 無論您的 Python 測試經驗如何,這些技術都將簡化您的工作流程並創建強大的測試套件。


挑戰:單元測試中的外部依賴

現代軟體經常與外部系統互動—資料庫、檔案系統、Web API 等。當這些交互作用滲透到單元測試中時,會導致:

  • 較慢的測試:實際 I/O 操作顯著增加運行時間。
  • 不穩定的測試:網路或檔案系統問題可能會破壞您的測試套件。
  • 困難的調試:不正確的修補會導致神秘的AttributeError訊息或部分模擬。

開發人員、QA 工程師和專案經理都受益於更乾淨、更可靠的測試。 隨機失敗或存取真實服務的測試會破壞 CI/CD 管道並減緩開發速度。 有效隔離外部依賴關係至關重要。 但是我們如何確保正確的模擬,同時避免常見的陷阱?


避免「模擬地獄」的七個技巧

以下七種技術提供了一個框架 - 一個「模擬健康」清單 - 讓您的測試保持高效、精確和快速。


1.使用補丁,未定義

一個常見的錯誤是在函數的定義處而不是呼叫它的地方修補函數。 Python 會取代被測試模組中的符號,因此您必須在該模組的導入上下文中進行修補。

<code class="language-python"># my_module.py
from some.lib import foo

def do_things():
    foo("hello")</code>
登入後複製
登入後複製
登入後複製
  • 錯誤: @patch("some.lib.foo")
  • 正確: @patch("my_module.foo")

修補my_module.foo可確保在測試使用它的任何地方進行替換。


2.模組與符號修補:精準度很重要

您可以替換單一函數/類別或整個模組。

  1. 符號級補丁:取代特定函數或類別:
<code class="language-python"># my_module.py
from some.lib import foo

def do_things():
    foo("hello")</code>
登入後複製
登入後複製
登入後複製
  1. 模組級補丁:MagicMock 取代整個模組。 每個函數/類別都成為一個模擬:
<code class="language-python">from unittest.mock import patch

with patch("my_module.foo") as mock_foo:
    mock_foo.return_value = "bar"</code>
登入後複製

如果您的程式碼呼叫其他 my_module 屬性,請在 mock_mod 上定義它們或面對 AttributeError


3.驗證實際導入,而不僅僅是回溯

回溯可能會產生誤導。 關鍵是你的程式碼如何導入函數。總是:

  1. 開啟正在測試的檔案(例如,my_module.py)。
  2. 找到導入語句,例如:
<code class="language-python">with patch("my_module") as mock_mod:
    mock_mod.foo.return_value = "bar"
    #  Define all attributes your code calls!</code>
登入後複製

<code class="language-python">from mypackage.submodule import function_one</code>
登入後複製
  1. 修補確切的命名空間:
    • 如果看到sub.function_one(),請修補"my_module.sub.function_one"
    • 如果看到from mypackage.submodule import function_one,請修補"my_module.function_one"

4.透過修補外部呼叫來隔離測試

模擬外部資源(網路請求、檔案 I/O、系統指令)的呼叫:

  • 防止緩慢或脆弱的測試操作。
  • 確保僅測試您的程式碼,而不是外部依賴項。

例如,如果您的函數讀取檔案:

<code class="language-python">import mypackage.submodule as sub</code>
登入後複製

在你的測驗中修補它:

<code class="language-python">def read_config(path):
    with open(path, 'r') as f:
        return f.read()</code>
登入後複製

5.選擇正確的模擬等級:高與低

模擬處理外部資源的整個方法或修補單一函式庫呼叫。 根據您要驗證的內容進行選擇。

  1. 進階補丁:
<code class="language-python">from unittest.mock import patch

@patch("builtins.open", create=True)
def test_read_config(mock_open):
    mock_open.return_value.read.return_value = "test config"
    result = read_config("dummy_path")
    assert result == "test config"</code>
登入後複製
  1. 低階補丁:
<code class="language-python">class MyClass:
    def do_network_call(self):
        pass

@patch.object(MyClass, "do_network_call", return_value="mocked")
def test_something(mock_call):
    # The real network call is never made
    ...</code>
登入後複製

進階修補程式速度更快,但會跳過內部方法測試。低級補丁提供更精細的控制,但可能更複雜。


6.為模擬模組分配屬性

當修補整個模組時,它會變成一個沒有預設屬性的MagicMock()。如果您的程式碼呼叫:

<code class="language-python">@patch("my_module.read_file")
@patch("my_module.fetch_data_from_api")
def test_something(mock_fetch, mock_read):
    ...</code>
登入後複製

在您的測驗中:

<code class="language-python">import my_service

my_service.configure()
my_service.restart()</code>
登入後複製

忘記定義屬性會導致AttributeError: Mock object has no attribute 'restart'


7.修補高層呼叫者作為最後的手段

如果呼叫堆疊太複雜,請修補高階函數以防止達到更深層的導入。例如:

<code class="language-python">with patch("path.to.my_service") as mock_service:
    mock_service.configure.return_value = None
    mock_service.restart.return_value = None
    ...</code>
登入後複製

當你不需要測驗complex_operation時:

<code class="language-python">def complex_operation():
    # Calls multiple external functions
    pass</code>
登入後複製

這加快了測試速度,但繞過了測試complex_operation的內部結構。


影響與好處

應用這些「模擬健康」策略會產生:

  • 更快的測試:減少對實際 I/O 或網路操作的依賴。
  • 更少的神秘錯誤:正確的修補可以最大限度地減少AttributeError和類似問題。
  • 增強信心:穩定、隔離的測試套件可確保可靠的部署。

使用這些實踐的團隊通常會看到更可靠的 CI/CD 管道、更少的調試和更有效率的功能開發。

<code class="language-python"># my_module.py
from some.lib import foo

def do_things():
    foo("hello")</code>
登入後複製
登入後複製
登入後複製

此圖說明了正確的修補如何攔截外部調用,從而使測試更加順利。


未來的考量

Python 模擬功能非常強大。 考慮:

  • 替代函式庫: pytest-mock 提供簡化的語法。
  • 自動「模擬運作狀況」檢查:建立一個工具來驗證匯入的修補程式位置。
  • 整合測試:當模擬隱藏太多時,在受控環境中添加針對真實服務的單獨測試。

立即改進您的測試套件! 應用這些技術並分享您的結果。 讓我們在 Python 專案中保持優秀的「Mocking Health」!

以上是在 Python 測試中避免「模擬地獄」的實用技巧的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板