測試與程式碼品質

單元測試、整合測試、UI 測試的差異:一次整理測試金字塔

學習開發時,一定會遇到一道牆。

閱讀 4 分鐘
單元測試、整合測試、UI 測試的差異:一次整理測試金字塔 封面圖

學習開發時,一定會遇到一道牆。

「單元測試、整合測試、UI 測試……到底有什麼不同?」

先說結論。

三種測試的驗證範圍不同。單元測試檢查一個函式,整合測試檢查模組之間的連接,UI 測試則確認完整的使用者畫面流程。

告訴我們該如何混合這三種測試的指引,就是「測試金字塔」。今天會結合我的經驗,簡單說明這個概念。


先看重點摘要

為了方便忙碌的讀者,先整理如下。

  1. 單元測試:只驗證一個函式或類別。快速且大量撰寫。
  2. 整合測試:驗證多個模組連接後是否正常運作。數量適中。
  3. UI 測試:像點擊實際畫面一樣驗證完整流程。慢且少量。
  4. 測試金字塔:底部的單元測試要寬、頂部的 UI 測試要窄。

記住這四行,就理解一半了。接著逐一拆解。


單元測試、整合測試、UI 測試有什麼不同?

這是最容易混淆的部分,先從比喻開始。

想像一下你正在製造汽車。

單元測試就像檢查一顆螺絲或螺栓是否符合規格,是非常小的單位。

整合測試則是檢查引擎和變速箱連接後,是否能順利咬合運轉。

UI 測試就像坐進完成的車裡、發動引擎並實際駕駛。

用表格比較,可以整理如下。

分類 驗證範圍 速度 撰寫數量
單元測試 一個函式或類別 非常快(ms) 多
整合測試 模組間整合 普通(以秒計) 中等
UI 測試 完整使用者流程 慢(數十秒) 少

核心差異在於「要看多大的範圍」。

範圍越廣,越接近實際使用環境,但也會越慢,管理更加棘手。


單元測試就是這種感覺

光說不容易有概念,來看一個簡短範例。

下面是只鎖定驗證兩個數字相加函式的單元測試。

// 只驗證加法函式的單元測試
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

@Test func 加法_2_相加_3是_5() {
    #expect(add(2, 3) == 5)
}

不需要外部資料庫,也不需要畫面,只要單獨取出一個函式確認即可。

因此執行會在眨眼間完成。即使執行數百個,也只需要幾秒。

我最常使用單元測試,就是喜歡這種快速感。修改程式碼後可以立即確認。

單元測試就是因為這種亮起綠燈的成就感,讓人持續使用
單元測試就是因為這種亮起綠燈的成就感,讓人持續使用

什麼是測試金字塔?

現在進入今天的主角:測試金字塔。

這是 Mike Cohn 在 2009 年的著作《Succeeding with Agile》中提出的概念,主張將測試堆成三角形。

  • 最底層(寬):單元測試 — 最多
  • 中間:整合測試 — 適量
  • 最上層(窄):UI 測試 — 最少

為什麼是這種形狀?

因為越往下越快速、便宜。即使執行數千個單元測試,也不會造成負擔。

相反地,UI 測試速度慢,畫面稍微變動就很容易失敗,維護成本也很高。

大量使用便宜又快速的測試,少量使用昂貴又緩慢的測試。這就是測試金字塔的全部。

如果把金字塔倒過來堆,就稱為「冰淇淋甜筒」反模式。這代表 UI 測試很多,卻沒有單元測試,很容易被緩慢又脆弱的測試拖住。


那比例該怎麼設定?

這是很多人都想知道的問題。

常被引用的標準大約是單元 70%:整合 20%:UI 10%。

不過這不是絕對法則,應根據專案性質彈性調整。

例如後端 API 伺服器,提高整合測試的比例會更實際。Google 等組織也強調維持「金字塔形狀」的方向,而不是追求精確數字。

再分享一個我親身經歷得到的建議。

不要執著於數字。重要的是養成檢查「是否過度依賴緩慢且容易失敗的測試」的習慣。


常見問題(Q&A)

Q. E2E 測試和 UI 測試是一樣的嗎?

兩者幾乎會互換使用,但嚴格來說不同。E2E(End-to-End)指從使用者角度驗證完整流程,UI 測試則著重於其中的畫面操作。實務上常常混用。

Q. 只要把整合測試寫好,就可以不做單元測試嗎?

不建議。整合測試很難精確指出問題發生在哪裡。有單元測試才能快速縮小原因範圍。

Q. 測試必須一開始就全部寫好嗎?

不用。我建議先從核心邏輯的單元測試開始。很多人試圖一開始就建立完美的金字塔,最後反而精疲力竭。

底部要寬、頂部要窄。記住這個形狀就好
底部要寬、頂部要窄。記住這個形狀就好

這三者的差異只看文字可能很抽象,但親自撰寫程式碼後就會有切身體會。

用今天學到的內容,先為一個小函式加上單元測試吧。累積這種感覺後,其他部分自然會跟上。加油!