iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
IT 管理

Playwright + Test Design + AI Agent:自動化測試實戰系列 第 25

第 25 天:以靜制動 — 使用 Mock Server 隔離外部依賴環境

  • 分享至 

  • xImage
  •  

真正的武學高手,講求的是「以靜制動」。他們不盲目出招,而是靜靜觀察敵人的破綻,當對手一有動作,便能精準反擊。在自動化測試的世界裡,外部依賴就像是難以預測的對手,它們的行為可能不穩定。而今天,我們將學習如何運用 Mock Server,創造一個可控的「靜」態環境,讓我們的測試能夠專注於自身的邏輯,達到「以靜制動」的最高境界。

內功心法:為何需要 Mock Server

在開發應用程式往往需要與多個外部服務進行整合測試,包含後端的 API、第三方支付系統、簡訊服務或雲端儲存服務等等。這些外部依賴的服務雖然在生產環境中不可或缺,但在整合測試或是 E2E 測試的時候,卻有可能因為依賴外部服務而造成測試無法每次通過的現象。

測試穩定性問題

外部服務可能因網路延遲、服務中斷或資料異動而導致測試結果不一致。這種不確定性會降低團隊對測試結果的信任,當測試失敗時,如果是 False Alert 可能會讓我們難以判斷是程式碼問題還是外部服務問題。

執行效能瓶頸

每次測試都需要等待外部服務回應,特別是當測試案例數量時,這些網路請求的累積延遲會顯著增加整體執行時間,影響 CI/CD 流程的效率。

成本與使用限制

某些外部服務採用計量收費模式,或設有 API 呼叫次數限制。頻繁的測試執行可能產生不必要的費用,或很容易達到配額上限。

場景覆蓋困難

在真實環境中難以模擬特定情境,例如網路逾時、特定 HTTP 狀態碼(500、403)、或罕見的測試情境。

招式演練:Mock Server 三種境界

運用 Mock Server 就像是修煉武功,可分為不同的境界:

第一境:初窺門徑 - 手動 Mocking

這是最基礎的境界。在測試程式碼中,直接使用測試框架(如 Playwright)提供的工具來攔截網路請求,並手動提供假的回應。

test('使用 page.route 攔截請求', async ({ page }) => {
  await page.route('**/api/orders', async route => {
    await route.fulfill({
      status: 201,
      contentType: 'application/json',
      body: JSON.stringify({ id: 'mock-id', status: 'Created' })
    });
  });
  
  // 執行測試步驟
});

這個方式的優點是不需額外工具,且測試框架支援的關係能夠快速實作。測試邏輯與 Mock 程式碼會寫在一起,通常是無法在不同的多個測試案例中重複使用。

第二境:融會貫通 - 獨立 Mock Server

建立一個獨立運行的 Mock Server 服務,在測試開始前啟動。常見的工具包括:

  • WireMock:Java 生態系的成熟方案
  • JSON Server:適合快速建立 RESTful API Mock
  • MSW (Mock Service Worker):支援瀏覽器和 Node.js 環境

這個方式的優點是 Mock 邏輯與測試程式碼完全獨立,不同測試可以共用同一套 Mock 邏輯。但需要額外設定與維護一個獨立的服務。

第三境:無招勝有招 - 使用 OpenAPI / Swagger

若專案已有 API 規格文件(如 OpenAPI/Swagger),可以結合工具自動生成 Mock Server 設定。這種方式能確保 Mock 行為與實際 API 規格一致,減少手動維護成本。搭配 spec-kit 等工具,可以根據 API 規格自動產生對應的 Mock 設定檔,進一步提升開發效率。

實際應用範例

以下範例展示如何使用獨立 Mock Server 進行測試:

import { test, expect } from '@playwright/test';

test.describe('訂單建立測試', () => {
  test('成功建立訂單並顯示確認訊息', async ({ page }) => {
    // 使用 page.route() 模擬由訂單建立請求後的回應。
    await page.route('http://localhost:8080/orders', async route => {
      // 當有發送請假,會提供一個成功的假回應。
      await route.fulfill({
        status: 201,
        contentType: 'application/json',
        body: JSON.stringify({ id: 'mock-order-123', status: 'Created' })
      });
    });

    // 導航到訂單頁面,此時前端會載入。
    await page.goto('http://localhost:3000/order-form');

    // 填寫表單資訊。
    await page.getByLabel('Product Name').fill('iPhone 15');
    await page.getByLabel('Quantity').fill('1');

    // 點擊按鈕後,會送出 POST 請求到 /orders,但這個請求會被 Playwright 用 page.route() 攔截。
    await page.getByRole('button', { name: 'Submit Order' }).click();

    // 驗證頁面上是否顯示了成功訊息。
    // 這裡我們預期前端會根據 201 的成功回應來顯示訊息。
    await expect(page.getByText('訂單建立成功!')).toBeVisible();
    await expect(page.getByText('訂單編號: mock-order-123')).toBeVisible();
  });
});

最佳實務:Mock Data 的維護策略

為專案選擇最適合的 Mocking 層級。對於簡單的單元測試或小型專案,手動 Mocking 就足夠了,我們可以直接在程式碼中模擬資料或 API 的回應行為。然而,當專案規模擴大,需要模擬複雜的 API 行為時,我們就該建立一個獨立的 Mock Server,讓前端和測試團隊能獨立作業,不受後端開發進度的限制。對於那些擁有明確 API 規格檔案的專案,則可以進一步採用自動化生成方案,例如利用 OpenAPI 檔案,確保測試資料始終與 API 規格保持一致。

一旦我們選擇了 Mocking 策略,可以確保測試資料與真實 API 的可以盡量貼近。因為當它們的行為不一致時,測試結果就可能不值得參考,可能導致會「測試通過,但正式環境上失敗」。我們可以採用契約測試,這種自動化方法來保證前端和後端服務之間的 API 協定能保持同步,讓我們即使用測試資料進行測試,也能保證基本的功能有被驗證過。

儘管 Mocking 能提供快速且穩定的測試環境,但它永遠無法完全取代真實環境。因此,我們仍然需要透過少量的 E2E 測試,來驗證與真實外部服務(如金流系統、第三方登入等)的整合。這些測試雖然執行時間較長,但卻是最貼近使用者流程的測試,可以確保使用者的快樂路徑不會有太大的問題。

最後,合理設計 Mock 資料。這些資料盡可能接近真實場景,不僅要考慮正確的資料格式,更要包含各種邊界值、異常情境,以及多樣化的使用者資料。透過精心設計的測試資料,我們才能讓測試涵蓋度的範圍,避免潛在的盲點。

收功:掌控外部依賴的智慧

Mock Server 是隔離外部依賴的有效工具,能夠顯著提升測試的穩定性、速度和可維護性。透過選擇適合的實作方式,並結合自動化工具,可以建立一套高效的測試架構,確保軟體品質的同時,也維持開發團隊的生產力。


上一篇
第 24 天:氣化萬物 - 打造 Mock 測試資料的內功心法
下一篇
第 26 天:雷霆萬鈞 - 平行化測試,加速測試執行效率
系列文
Playwright + Test Design + AI Agent:自動化測試實戰26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言