iT邦幫忙

2026 iThome 鐵人賽

DAY 18
1
Software Development

從脆弱腳本到可信任測試平台:自動化測試架構30天系列 第 18

Day 18|自動化測試如何處理外部服務:Mock、Stub 與真實整合環境的抉擇

  • 分享至 

  • xImage
  •  

「如果你的自動化測試因為第三方金流 API 伺服器掛掉、或是簡訊驗證碼發送次數超限而跳紅燈,那失敗的不是你的產品,而是你的測試隔離策略。」

大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與各種外部依賴服務奮戰的自動化測試工程師(SDET)。

在上集 Day 16 與 Day 17 中,我們解決了測試資料的動態建立與清理問題。

然而在現代的 Web 與 App 系統中,我們的產品很少是孤立運作的。系統內部常充斥著各種外部服務的串接:

  • 金流刷卡 Gateway(Stripe, TapPay, 綠界)
  • 簡訊與 Email 驗證碼發送服務(Twilio, SendGrid)
  • 第三方 OAuth 登入(Google, Apple ID, LINE)
  • 地圖與物流運算 API(Google Maps API)

當我們在執行自動化測試時,這些外部服務常常成為最大的 Flaky 來源執行瓶頸

今天這篇文章,我們就來好好聊聊:自動化測試該如何處理解外依賴?Mock、Stub、Fake 到底有什麼不同?我們又該如何在『真實呼叫』與『模擬隔離』之間做抉擇?

一、 釐清概念:Mock、Stub、Fake 到底差在哪?

在討論隔離策略前,我們先用一張圖與簡單比喻搞懂這三個常被混淆的名詞:

                    ┌─────────────────────────┐
                    │    外部服務模擬 3 兄弟   │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
1. Stub (罐頭回應)            2. Mock (行為驗證)         3. Fake (簡化版實作)
- 回傳固定的 JSON/狀態碼   - 驗證 API 是否有被正確呼叫   - 記憶體內的簡易資料庫/服務

  • Stub(罐頭回應 / 替代物)
    • 目的:提供預設好的固定回傳值,不關心過程。
    • 例子:無論傳什麼卡號給信用卡 API,只要呼叫 /pay,Stub 就直接固定回傳 {"status": "SUCCESS", "transaction_id": "tx_123"}
  • Mock(模擬物件與行為驗證)
    • 目的:除了提供回傳值,還會斷言(Assert)該 API 是否有被呼叫、呼叫了幾次、傳入的參數是否正確
    • 例子:驗證使用者按下結帳後,產品程式碼『確實呼叫了 1 次』Stripe API,且帶入的金額參數為 100
  • Fake(偽造實作 / 簡易版)
    • 目的:具備真實商業邏輯,但採用了簡化的實作方式(通常用於測試環境)。
    • 例子:在 Local / Test 環境使用 SQLite 替代 Production 的 PostgreSQL,或者使用 SQLite 寫一個 InMemory 的簡訊發送紀錄佇列。

二、 抉擇矩陣:什麼時候該 Mock?什麼時候必須真的呼叫?

身為 SDET,我們不能無腦地把所有外部 API 全部 Mock 掉,否則測試會失去真實性;但也不能全部呼叫真 API,否則測試會變得極慢且不可靠。

┌────────────────────────────────────────────────────────────────────────┐
│                      外部服務處置抉擇矩陣                                │
├────────────────────────────────────────────────────────────────────────┤
│ 1. 每次呼叫會產生真實費用 (如: 簡訊、AI API, Google Maps)  ──▶ 必須 Mock │
│ 2. 存在嚴格的 Rate Limit (如: 發送驗證碼限制 5 次/分)       ──▶ 必須 Mock │
│ 3. 無法輕易在測試環境觸發的情境 (如: 銀行扣款失敗 500)       ──▶ 必須 Mock │
│ 4. 產品核心商業串接 (如: Smoke Test 驗證真實金流 Sandbox)   ──▶ 真實呼叫  │
└─────────────────────────────────────────────────────────────────────────┘

三、 Python / pytest 實戰:2 種主流 Mock 技巧

在 Web 與 App 自動化測試中,我們主要可以在 後端服務層前端/網路層 進行 Mock。

技巧 1:使用 Playwright 網路層攔截與 Mock(適合 Web / Webview 測試)

如果你的 Web 或 App Webview 在前端會直接發送 HTTP 請求給第三方 API,我們可以使用 Playwright 的 page.route() 在瀏覽器網路層直接攔截,不需要修改任何後端 Code!

Python

# tests/test_payment_mock.py
from playwright.sync_api import Page, expect

def test_checkout_handles_payment_failure_gracefully(page: Page):
    """
    測試情境:模擬第三方金流 API 回傳 500 錯誤時,前端頁面是否會正確顯示友善提示
    """
    # 1. 使用 route() 攔截第三方金流 API 請求,並 Mock 回傳 500 錯誤
    def handle_payment_api(route):
        route.fulfill(
            status=500,
            content_type="application/json",
            body='{"code": "PAYMENT_FAILED", "message": "Bank server busy"}'
        )

    # 攔截包含特定 URL pattern 的 API 呼叫
    page.route("**/api/third-party/payment", handle_payment_api)

    # 2. 執行正常的 UI 操作
    page.goto("https://staging.example.com/checkout")
    page.get_by_test_id("pay-btn").click()

    # 3. Assert: 驗證前端成功捕捉 500 錯誤,並顯示「付款失敗,請稍後再試」
    expect(page.get_by_test_id("error-banner")).to_be_visible()
    expect(page.get_by_test_id("error-banner")).to_have_text("付款失敗,請稍後再試")

技巧 2:在 pytest 中使用 unittest.mock / pytest-mock(適合後端 API 測試)

如果外部服務是由你的後端伺服器(Python/FastAPI/Django)在背景呼叫,我們可以在 Python 測試中使用 mocker 替換掉 API 呼叫類別:

Python

# tests/test_order_service.py
import pytest
from services.order_service import OrderService

def test_process_order_success(mocker):
    """
    測試情境:驗證訂單服務在第三方簡訊發送成功時,能正常完成訂單流程
    """
    # 1. 使用 pytest-mock 攔截 SMSClient 的 send_sms 方法,強制回傳 True
    mock_sms = mocker.patch("utils.sms_client.SMSClient.send_sms", return_value=True)

    order_service = OrderService()

    # 2. 執行訂單處理邏輯
    result = order_service.process_order(order_id="ORD_12345")

    # 3. Assert 1: 驗證商業邏輯回傳成功
    assert result["status"] == "COMPLETED"

    # 4. Assert 2 (Mock 行為驗證): 驗證 SMSClient 確實有被呼叫了 1 次
    mock_sms.assert_called_once_with(phone="0912345678", message=mocker.ANY)

四、 第一線 SDET 的實務建議

  1. 為 Mock 資料建立「契約測試(Contract Test)」
    Mock 的最大風險是:『你的 Mock 很完美,但真實的第三方 API 悄悄改版了!』
    建議定期(如每週一次)運行一組極少量的「真實 Integration Smoke Test」,確保你 Mock 的 JSON Schema 與真實第三方 API 保持一致。
  2. 優先在 Sandbox / Sandbox 環境進行 Smoke Test
    如果第三方有提供官方 Sandbox(如 Stripe Test Key),核心 Path(Happy Path)儘量走一次真實 Sandbox;而各種異常情境(如 500 錯誤、Timeout、卡號過期)則交給 Mock 處理。
  3. 不要在 E2E 測試中測試「別人的系統」
    你的目標是驗證「你的產品在面對各種外部狀態時的應對能力」,而不是去驗證「Stripe 的刷卡系統算不算得對」。分清邊界,才能寫出高速且穩定的自動化測試。

明日預告

討論完外部服務與 Mock 的抉擇後,接下來我們要比較 UI 測試與 API 測試在價值上的根本差異:

「為什麼在自動化測試的世界裡,API 測試通常比 UI 測試划算 10 倍?我們該如何安排這兩者的比例?」

明天 Day 19,我們將一起來剖析:《 Day 19|為什麼 API 測試通常比 UI 測試更划算?效益與維護成本深度剖析 》


上一篇
Day 17|測試資料的建立與清理策略:預建、API 產生與 Fixture 實戰
下一篇
Day 19|為什麼API測試通常比UI測試更划算?效益與維護成本深度剖析
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言