5.1 為什麼要測試?測試金字塔#
沒有測試的代價#
你改了一行程式碼,覺得沒問題就上線了——結果弄壞了三個月前寫的另一個功能。這叫回歸(regression),是沒有測試的專案最常見的災難。
沒有測試的團隊特徵:
- 不敢改程式:怕動到不知道的東西,技術債越積越多
- 人肉測試:每次上線前手動點一遍所有功能,慢又容易漏
- 重構是賭博:不知道改完之後對不對
測試解決的核心問題:程式碼的每一次變更,都有機器自動驗證對不對。
測試金字塔#
/ E2E \ 少:模擬真實使用者的完整流程(慢、貴)
/ 整合測試 \ 中:驗證多個模組組合起來的行為
/ 單元測試 \ 多:驗證單一函式的行為(快、便宜)
| 層級 | 測什麼 | 速度 | 數量 |
|---|---|---|---|
| 單元測試 | 單一函式 / 模組 | 毫秒級 | 最多 |
| 整合測試 | 多模組組合、API + 資料庫 | 秒級 | 適量 |
| E2E 測試 | 完整使用流程 | 分鐘級 | 少量關鍵路徑 |
原則:底層的單元測試要多、要快;頂層的 E2E 只測關鍵路徑。反過來(大量 E2E)的專案會又慢又不穩定。
好測試的特徵#
一個單元測試只驗證一件事,結構是三段式(AAA):
test("divide 應該正確計算", () => {
// Arrange(準備)
const result = divide(10, 2);
// Assert(驗證)
assert.equal(result, 5);
});
- 快:單元測試應該毫秒級完成,慢了你就不會常跑
- 獨立:測試之間不互相依賴,順序打亂也能過
- 可重複:任何時候、任何機器上跑結果都一樣
測試什麼時候寫?#
- TDD(Test-Driven Development):先寫測試再寫程式,適合邏輯明確的功能
- 實務折衷:至少在修 bug 時先寫一個「會失敗的測試」重現 bug,修好後它變成回歸防護
心法:測試不是為了「證明程式對」,而是為了「讓你有勇氣改程式」。有測試的重構是工程,沒測試的重構是賭博。