前端最常見的三種測試類型是:
- 單元測試
- 整合測試
- E2E 測試
理解這三種測試的差異,是建立前端測試架構的起點。接著,我們再比較測試金字塔與測試獎杯,看看它們如何協助我們分配測試資源。
單元測試、整合測試與 E2E 測試有什麼差異?
單元測試、整合測試與 E2E 測試,主要差別在於一次驗證多大的範圍。
範圍越小,測試通常越快,也越容易找出錯誤原因;範圍越大,越接近使用者真正操作產品的情況,但需要準備的環境和資料也會增加。
假設我們要測試一個登入功能,其中包含 Email 格式驗證、登入表單、API(前端與後端交換資料的介面)請求,以及登入後的頁面跳轉。
我們可以將測試分成以下三個層次:
- 單元測試
- 整合測試
- 端對端測試
單元測試(Unit Test)
單元測試會取出一小段可以獨立運作的程式碼,確認它在不同輸入下是否產生正確結果。
以前面的登入功能為例,我們可以單獨測試 validateEmail 函式,確認合法的 Email 回傳 true,缺少 @ 或網域時回傳 false 等等測試。
這類測試不需要渲染 React 元件,也不用真的呼叫 API,所以執行速度很快。一旦測試失敗,問題通常就在被測試的函式附近。
單元測試很適合純函式、條件判斷、邏輯運算等單純、不需要和外部連接的函式。
整合測試(Integration Test)
整合測試會將多個模組或元件一起測試,確認它們整合後能否正確合作。
例如前端的整合測試經常會同時包含 React 元件、狀態管理(State)、使用者操作,以及 API 回應等。
一樣以登入表單為例,整合測試可以先渲染整個表單,模擬使用者輸入帳號密碼並按下登入,再確認程式是否以正確的方式呼叫 API,以及 API 回傳錯誤時,畫面是否顯示對應訊息。
這段流程涵蓋了輸入欄位、表單狀態、驗證邏輯與 API 互動。測試關注的是使用者完成一個功能的整體流程,而不是個別函式或元件的內部實作。
因此,即使內部實作方式改變,只要對外行為維持一致,整合測試通常仍然能夠通過。
另外,整合測試的範圍沒有絕對的界線,只要同時驗證多個元件、模組或服務之間的合作行為,都可以視為整合測試。
端對端測試(End-to-End Test)
端對端測試通常簡稱 E2E 測試,它會模擬使用者完成一個完整的操作流程,和整合測試不同的是,E2E 測試會透過真實瀏覽器操作,並驗證前端、後端與資料庫等整個系統是否能共同完成一個完整流程,讓測試盡量使用接近正式環境的系統來驗證整個流程。
例如登入功能的 E2E 測試會啟動瀏覽器、進入登入頁、填寫帳號密碼、送出表單,等待系統完成登入流程,並確認使用者成功進入會員頁。這項測試同時檢查前端、後端、資料庫、網路請求與頁面跳轉是否能一起運作。
E2E 測試很接近真實使用情況,因此 E2E 測試通過後,通常能提供我們很高的信心。但相對的,代價是執行速度較慢,測試也可能受到網路、測試資料、登入狀態與外部服務影響。
測試失敗時,問題可能出現在整條流程的任何位置,排查時間通常會比較長。
整合測試與 E2E 測試的差異
整合測試和 E2E 測試都可以模擬使用者操作,主要差別在於:測試涵蓋多大的範圍,以及是否使用真實的後端與資料庫。
整合測試通常只測試幾個元件或模組能不能正確合作。後端 API、付款服務等外部依賴,通常會用 Mock 取代。
E2E 測試則會盡量使用完整系統,從瀏覽器操作開始,經過前端、後端與資料庫,最後確認使用者能不能完成整個流程。
以登入功能為例:
- 整合測試:操作登入表單,使用 Mock API 回傳成功或失敗結果,再確認畫面是否正確更新。
- E2E 測試:開啟真實瀏覽器登入,請求真正的後端與資料庫,最後確認使用者成功進入會員頁。
| 面向 | 整合測試 | E2E 測試 |
|---|---|---|
| 測試範圍 | 幾個元件或模組 | 完整使用流程 |
| 外部依賴 | 通常使用 Mock | 盡量使用真實系統 |
| 執行速度 | 較快 | 較慢 |
| 問題定位 | 範圍較小,較容易排查 | 可能出現在系統任何環節 |
| 測試目的 | 確認部分功能能正確合作 | 確認使用者能完成完整流程 |
三種常見測試可以簡單整理如下:
| 面向 | 單元測試 | 整合測試 | E2E 測試 |
|---|---|---|---|
| 測試範圍 | 單一函式或邏輯 | 多個元件或模組 | 完整系統流程 |
| 登入範例 | 驗證 Email 格式 | 操作表單並模擬 API 回應 | 從登入頁一路進入會員頁 |
| 執行速度 | 快 | 中等 | 較慢 |
| 問題定位 | 容易 | 中等 | 較困難 |
| 提供的信心 | 確認單一邏輯正確 | 確認多個部分能合作 | 確認真實流程能完成 |
| 常見工具 | Vitest | Vitest、React Testing Library、MSW | Playwright |
一般來說,測試越接近使用者真正操作產品的方式,提供的信心越高。不過,測試越接近完整系統,執行速度、穩定性與維護成本通常也會越高。
因此,不是所有功能都需要寫成 E2E 測試,而是應該搭配單元測試、整合測試與 E2E 測試,在信心、速度與維護成本之間取得平衡。
那我們要如何分配 3 種測試的資源與比例呢?
這就可以提到測試金字塔與測試獎杯。
測試金字塔是什麼?
測試金字塔(Testing Pyramid)由 Mike Cohn 在 2009 年出版的《Succeeding with Agile》中推廣。
它的核心概念很簡單:
- 底層放大量快速、範圍小的測試
- 中層放適量的整合測試
- 頂層只保留少量完整流程測試
| 金字塔位置 | 測試類型 | 數量 | 特性 |
|---|---|---|---|
| 頂層 | E2E 測試 | 少 | 接近真實流程,但速度慢、維護成本高 |
| 中層 | 整合測試 | 適量 | 驗證多個元件或模組能否合作 |
| 底層 | 單元測試 | 多 | 快速、穩定,容易找到問題 |
底層比較寬,是因為單元測試能用很低的成本測試大量條件。
例如折扣計算可能包含原價、折扣碼、滿額優惠和無庫存等情況。這些條件可以透過單元測試快速驗證,不需要每次都啟動瀏覽器、加入購物車並完成結帳。
不過,專案仍然需要少量 E2E 測試。因為單元測試與整合測試通過,不代表前端、後端和資料庫實際連接後一定能正常運作。
像是登入、註冊、結帳和付款這種很重要的流程,就很適合使用 E2E 測試做最後確認。
避免變成冰淇淋甜筒、沙漏型測試
如果一個專案只有少量單元測試,卻有大量 E2E 測試,測試結構就會像倒過來的金字塔,也稱為「冰淇淋甜筒」。
這種做法最大的問題是,許多 E2E 測試都依賴同一套測試環境。只要登入服務、資料庫或共用 API 發生問題,原本正常的功能也可能跟著一起失敗,讓開發者很難第一時間判斷是真正的產品錯誤,還是測試環境出了問題。
最後,團隊可能花很多時間維護測試,而不是修正產品。
另一種常見情況是「沙漏型」結構:單元測試和 E2E 測試很多,但整合測試很少。
這種情況下,單元測試只能證明每個函式都能正常運作;E2E 測試則只能告訴你整個流程失敗了。中間缺少驗證多個元件合作的整合測試,因此當問題發生時,很難快速判斷是哪一個功能或模組出了問題,除錯成本也會提高。
所以簡單來說,這 2 種測試都是因為缺乏好的層級,造成測試沒通過時,較難追蹤問題。
測試金字塔的比例
測試金字塔有一個常見的分配比例是:
- 70% 單元測試
- 20% 整合測試
- 10% E2E 測試
但這些數字只是參考。實際還是要根據專案、功能、成本來調整。
為什麼前端出現測試獎杯?
Kent C. Dodds 在 2018 年提出測試獎杯(Testing Trophy),用來描述 JavaScript 與前端應用更實用的測試分配方式。
它包含四個部分:
| 層級 | 常見工具 | 主要保護範圍 |
|---|---|---|
| E2E | Playwright | 少量重要的完整流程 |
| Integration | React Testing Library、MSW | 元件、狀態與 API 請求的合作 |
| Unit | Vitest | 純函式與複雜商業邏輯 |
| Static | TypeScript、ESLint | 型別、語法與程式規則 |
測試獎杯和測試金字塔最大的差異,是把最多資源放在整合測試,而不是單元測試。
因為前端頁面通常由多個元件、狀態和資料請求一起組成,甚至一個元件也會由多個小元件組成,因此只測試單一元件,不容易發現使用者實際操作的問題。
另外可以發現他多了一個層級: Static。
Static:在執行前攔截錯誤
Static 就是利用 TypeScript 和 ESLint 等工具,在開發時就先攔截錯誤、發現問題,例如型別錯誤、語法問題和不符合團隊規則的程式碼等等。
它們無法驗證登入或結帳流程,但能用很低的成本攔截大量基礎錯誤。
測試金字塔和測試獎杯有什麼差異?
| 面向 | 測試金字塔 | 測試獎杯 |
|---|---|---|
| 主要背景 | 傳統應用與服務架構 | JavaScript 與前端應用 |
| 最大投入 | 單元測試 | 整合測試 |
| 靜態檢查 | 通常沒有列入 | 明確列為底層 |
| 主要優勢 | 執行快速,容易定位問題 | 接近使用者行為 |
| 常見風險 | 過度隔離與過度 Mock | 測試範圍太大、設定變複雜 |
| E2E 定位 | 少量完整流程 | 少量關鍵流程 |
兩者並不互相衝突。
測試金字塔提醒我們,不要讓成本高的大範圍測試占據大多數;測試獎杯則提醒我們前端工程師,不要為了追求大量單元測試,把原本應該一起運作的功能全部拆開測試。
總結
這個單元介紹了單元測試、整合測試與 E2E 測試的差異,主要在於測試範圍不同。以及金字塔測試和獎杯測試。
後續也會和大家分享我在專案上,是如何使用 TypeScript、ESLint、Prettier 來嚴格規範團隊風格的。
