跳轉至

建立 2026-10-11 更新 2026-10-11

Mock 與隔離外部依賴

Mock 是在測試中用來「假扮」真實物件的替身:當程式依賴網路 API、資料庫、目前時間或隨機數時,用 mock 取代這些外部依賴,測試就能快速、穩定、可重複地執行,還能檢查程式是否用正確的參數呼叫了它們。pytest 本身不提供 mock,通常搭配 Python 標準函式庫的 unittest.mock,或對它做了 pytest 包裝的 pytest-mock 外掛。

什麼時候需要 mock?

依賴 不 mock 會怎樣
網路 API 慢、會因網路或對方服務異常而失敗、可能產生費用
寄信、簡訊、金流 測試會真的寄出去或扣款
目前時間 datetime.now() 結果每次不同,無法寫固定的預期值
隨機數 同上

反過來說,自己寫的純邏輯不需要 mock。mock 太多會讓測試只是在驗證「mock 的設定」,而不是程式真正的行為。

Mock 物件的基本用法

Mock 物件可以接受任何呼叫,並記錄被怎麼呼叫:

from unittest.mock import Mock

sender = Mock()
sender.send("amy@example.com", "歡迎加入")

sender.send.assert_called_once_with("amy@example.com", "歡迎加入")

設定回傳值或讓它拋出例外:

api = Mock()
api.get_price.return_value = 120
api.get_stock.side_effect = TimeoutError("連線逾時")

用 patch 替換模組裡的物件

假設有一個查匯率的模組:

src/my_package/exchange.py
import requests


def get_rate(currency):
    resp = requests.get(f"https://api.example.com/rates/{currency}")
    resp.raise_for_status()
    return resp.json()["rate"]


def to_twd(amount, currency):
    return round(amount * get_rate(currency), 0)

測試 to_twd 時,把 get_rate 換掉,就不會真的連網:

from unittest.mock import patch

from my_package import exchange


def test_to_twd():
    with patch("my_package.exchange.get_rate", return_value=32.5) as mock_rate:
        assert exchange.to_twd(100, "USD") == 3250
    mock_rate.assert_called_once_with("USD")

patch 的路徑該寫哪裡?

規則是:patch「被使用的地方」,而不是「被定義的地方」。如果 report.py 寫的是 from my_package.exchange import get_rate,report 模組裡已經有自己的 get_rate 名稱,這時要 patch my_package.report.get_rate;patch my_package.exchange.get_rate 不會影響 report。這是 mock 最常見的錯誤。

pytest-mock:更 pytest 風格的寫法

pytest-mock 提供 mocker fixture,用法與 patch 相同,但不需要 with 區塊,測試結束時自動還原:

uv add --dev pytest-mock
from my_package import exchange


def test_to_twd(mocker):
    mock_rate = mocker.patch("my_package.exchange.get_rate", return_value=32.5)
    assert exchange.to_twd(100, "USD") == 3250
    mock_rate.assert_called_once_with("USD")

用 autospec 防止寫錯

一般 Mock 接受任何方法名稱與參數,就算你打錯字也不會報錯,測試照樣通過。加上 autospec=True,mock 會照真實物件的簽名檢查呼叫方式:

mocker.patch("my_package.exchange.get_rate", autospec=True, return_value=32.5)

monkeypatch、mock 該選哪個?

  • 只需要換掉某個值或函式:用內建的 monkeypatch,簡單、不需額外套件。
  • 需要驗證呼叫(呼叫幾次、帶什麼參數)或模擬例外:用 unittest.mock 或 pytest-mock。
  • 更好的長期做法是在設計上讓依賴可以從外部傳入(依賴注入),測試時直接傳入假物件,就不需要 patch 任何路徑,詳見 寫出好測試。

推薦影音

How to Write Great Unit Tests in Python(ArjanCodes)

ArjanCodes 以一個天氣查詢服務(WeatherService)為例,從基本測試原則一路示範 monkeypatch、mock、fixture 與參數化,說明如何把依賴外部 API 的程式寫成好測試的樣子,約 23 分鐘。

Professional Python Testing with Mocks(NeuralNine)

NeuralNine 約 21 分鐘的 mock 專題,示範如何用 mock 取代外部依賴來測試 Python 程式,適合想多看幾個 mock 實例的讀者。