測試與程式碼品質

單元測試、整合測試、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. 測試必須一開始就全部寫好嗎?

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

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

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

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