軟體測試 — 測試金字塔 (Testing Pyramid)
2025年7月21日
軟體測試是每個軟體工程師都需要掌握的技能,因此我們將陸續來談這個主題,今天這篇文章會先概要介紹軟體測試的測試概覽,以及大家常聽到的測試金字塔 (Testing Pyramid) 。
什麼是測試? 誰該寫測試?
測試是軟體品質與穩定性的把關,確保軟體運行都符合需求,不會出現錯誤。測試的本質之一是找出錯誤,還記得在 如何做好 Code Review? 如何寫出更快通過 Code Review 的程式碼? 一文我們有提到,很多人可能會認為程式碼審查是在幫忙找錯誤,但先前微軟的研究指出,程式碼審查對協助找出錯誤的幫助有限。比起程式碼審查,測試更能協助找到錯誤,讓軟體上線前能把錯誤都排除,藉此把關軟體品質的存在。
在早期的軟體開發模式中,開發者鮮少需要自己寫測試,在那個年代,幾乎都是由 QA 團隊來負責測試的環節。然而,近年來軟體業界的趨勢有所改變,越來越多團隊發現由開發者負責某部分的測試,對於整體效率、品質都會有所提升。
這與先前在 軟體工程師該如何值班 (on-call)? 一文談到的概念相似,當某個人需要為自己所做的結果負責時,做出的成果品質會比較高,因為如果品質不高,後果不再是由別人幫忙扛,而是自己得負責。測試就是為軟體的品質把關,所以當測試是由開發者負責,當程式出問題時,就不能甩鍋說是 QA 沒測到。當沒辦法甩鍋,需要自己承擔時,在寫程式就會更加小心謹慎,以免未來的自己要幫現在的自己擦屁股。
雖說現在有不小部分的測試責任,是由開發者自己負責,有些團隊仍會有自動化測試工程師,來協助涵蓋更廣的面向,包含非功能性 (non-functional) 的測試,例如效能、安全性、可用性等面向的測試,甚至是幫忙開發測試使用的模擬伺服器 (mock server)。
測試金字塔 (Testing Pyramid)
前面談到測試的好處時,有特別強調對於軟體穩定性的好處;相信有些讀者可能會問,除了穩定性,也有效能測試、安全性測試等不同測試,為什麼只強調穩定性呢? 確實如果要廣義談軟體測試,有效能、安全性、無障礙 (a11y) 等不同類的測試。
而在本期與在接下來幾期的內容,我們會聚焦在討論穩定性相關的測試,因為這是絕大多數軟體工程師都要面對的;其他類的測試可能更專門一些,所以不會在這次系列文的範圍中。
具體來說,讓我們先談一個在軟體工程師都該知道,而且在面試經常被問到的概念 — 測試金字塔。可以從下圖看到,測試金字塔的組成,是由最底下的單元測試 (unit tests),到中間層的整合測試 (integration tests),再到最上層的端到端測試 (E2E tests)。
/\\
/ \\
/ \\
/ \\
/ \\
/ E2E \\
/ Tests \\
/--------------\\
/ Integration \\
/ Tests \\
/--------------------\\
/ Unit \\
/ Tests \\
----------------------------
從測試金字塔的概念來說,至少要有單元測試,而越有餘力時,越往上層的整合測試與端到端測試去覆蓋。當然,理想的狀況下,這些應該都要被覆蓋。在《Google 的軟體工程之道》一書當中提到,建議是 80-15-5 的分配,單元測試要覆蓋至少 80%,而整合測試 15%,以及端到端測試要覆蓋主要路徑。往下讓我們逐一講解這幾種測試是什麼。
單元測試 (Unit Tests)
單元測試顧名思義是在測某個單元,而這邊的單元通常可以是一個類、一個函式,或者一個元件。舉例來說,最簡單的單元測試,會像下面這樣
// 有一個函式,會把兩個數相加
function add(a, b) {
return a + b;
}
// 單元測試來確保該函式的行為如預期
describe("add function", () => {
it("should return the sum of two numbers", () => {
const result = add(2, 3);
expect(result).toBe(5);
});
});
單元測試範圍會比較小,也因此會比較獨立。因為範疇小且獨立,所以執行單一的單元測試會比較快,這邊指的獨立,是指可以單獨運行,不需用依賴其他的系統就能跑測試。在跑測試時,也可以把環境切開,可以不需用在生產環境中執行測試,而是在進到生產環境前就把測試跑完。
整合測試 (Integration Tests)
整合測試的範圍會比單元測試廣一點,通常會是一個子系統,獨立性不會像單元測試那麼高。很多時候,但一個功能自己運行沒問題,但當不同模組整合在一起,就可能會出現不如預期的狀況;因此,就會需要同時測試不同模組整合在一起,避免當整合時出問題。
你可能會好奇,怎麼會有單元都沒問題,整合卻出問題的狀況? 在軟體開發的世界中,這還真的很常發生。舉例來說,可能有版本對齊問題 (例如不同服務的版本沒有對齊)、可能非預期的副作用 (side effect),這些都是在寫單元測試時覆蓋不到的。
又或者說,在寫單元測試時,經常會用 mock (目前技術社群普遍不翻譯這個字,所以這邊保持用英文,mock 是指模擬的對象),而這時就可能出現因為寫 mock 都沒問題,但是在實際跑軟體時,模擬的元件卻出問題,這時就需要靠整合測試抓出來。
因為需要整合不同的模組,整合測試所需要花費的時間,通常比單元測試來得高。
端到端測試 (E2E Tests)
端到端測試的範圍最廣,會是以模擬使用者使用系統的方式,走過完整的流程。下面是目前社群中最熱門的開源 E2E 測試工具 Playwright 官網中的例子,可以看到就像模擬使用者在用網站時,測試的腳本就一步步模擬使用者的行為、每個行為後預期會發生的事。
import { test, expect } from "@playwright/test";
test("has title", async ({ page }) => {
// 模擬使用者進到頁面
await page.goto("<https://playwright.dev/>");
// 預期進到頁面後,會看到 Playwright 這個標題
await expect(page).toHaveTitle(/Playwright/);
});
test("get started link", async ({ page }) => {
await page.goto("<https://playwright.dev/>");
// 模擬使用者點擊 Get started 字樣的連結
await page.getByRole("link", { name: "Get started" }).click();
// 預期點完連結進到的頁面,會有 Installation 的標題
await expect(
page.getByRole("heading", { name: "Installation" })
).toBeVisible();
});
因爲橫跨整個系統的模組,基本上獨立性低 (整合性高),執行起來也比較耗費時間。
有讀者可能會問,假如我們有單元測試,能覆蓋某個單一模組,又有 E2E 測試可以覆蓋完整的使用者情境,這樣為什麼還需要整合測試?
確實直觀上來說,E2E 測試本身也會測試到不同模組之間的整合,因為假如整合有問題,使用者的角度來操作就無法順利使用;但是 E2E 測試本身有一些問題,是讓整合測試仍有存在價值的。
其中包含:
- E2E 測試是走過整個完整流程,所以如果出問題的是外部依賴的模組,那可能會出現團隊自己的程式碼沒問題,但仍無法通過測試的狀況。舉例來說,假如今天某個聊天機器人,背後是呼叫 OpenAI 的 API,而 OpenAI 的伺服器掛掉,導致 E2E 測試在模擬使用者的情況出現測試案例沒通過,但這時因為不是自身程式碼的問題,就會讓測試相對不穩 (俗稱有 flakiness),導致測試案例沒過,但沒辦法透過修改程式碼讓測試案例跑過。但假如是整合測試,可以去模擬對外部的依賴,來確保程式碼的整合是沒問題的。
- E2E 測試要測某些整合的狀況會相對困難,舉例來說,假如今天我們想測試一個狀況是 A 模組先呼叫後呼叫 B 模組才可行,而 B 模組先呼叫後才呼叫 A 模組則會進到某個錯誤處理的情境。從 E2E 測試的角度來看,這種狀況很難被測到,因為 E2E 是模擬使用者操作,所以一般都是照正常情境的 A 模組先呼叫,可能只有在極少數出問題時才會變成 B 模組先呼叫,這樣可能要跑非常多次測試才會出現一次異常,從測試角度來說很沒效率。但假如是整合測試,就可以直接去模擬 B 模組先呼叫的情境,藉此來測試錯誤處理的狀況。
因為上述的原因,以及前面提到的 E2E 測試跑起來比較耗時,讓整合測試仍有其存在的價值,也因此有「推薦多寫整合測試」這個說法。
如果想更了解 E2E 測試,推薦閱讀《軟體測試 — E2E 測試是什麼? 跟整合測試有什麼區別? 》一文。
對測試金字塔的反思
雖然測試金字塔是目前社群中比較廣為人知的,但是也有不少觀點不完全認同。在軟體測試領域活躍的開發者 Kent C. Dodds 就提出過不同觀點的測試金盃 (Testing Trophy),推薦有興趣的讀者可以一看這個介紹 (連結)。
.png?alt=media&token=fffe100a-41e3-4e0a-85f2-4fef636c966b)
Vercel 創辦人也曾發過一個推文,提到他認為整合測試應該要佔比最大,他的觀點背後的原因,可以在這個推文看到。
閱讀更多
以上是關於軟體測試的介紹,在 E+ 成長計畫中,我們更深入談軟體測試的各面向。對更深入了解這個主題,以及其他前後端開發、軟體工程主題感興趣的讀者,歡迎加入 E+ 成長計畫一起成長 (連結)。