測試策略
前言
你的程式碼真的「沒問題」嗎? 每次改完程式碼手動點一遍看看有沒有壞——這種方式在專案小的時候還能湊合,但當程式碼量增長到幾萬行、團隊擴展到十幾人時,「手動點點看」就是一場災難。
本章帶你理解軟體測試的核心策略,從測試金字塔到 TDD,建立系統化的品質保障思維。
這篇文章會帶你學什麼?
| 章節 | 內容 | 核心概念 |
|---|---|---|
| 第 1 章 | 測試金字塔 | 測試的層次與比例 |
| 第 2 章 | 單元測試實戰 | 如何寫好一個測試 |
| 第 3 章 | TDD 驅動開發 | 紅綠重構循環 |
| 第 4 章 | 測試策略選擇 | 不同場景的方案 |
學完本章,你將理解如何為專案選擇合適的測試策略,寫出有價值的測試,並透過 TDD 提升程式碼設計品質。
0. 全景圖:為什麼需要自動化測試?
想像你是一個建築工程師。每次修改圖紙後,你不會親自爬上每一層樓去檢查結構是否安全——你會依賴一套自動化的檢測系統。軟體測試就是程式碼世界的「結構檢測系統」。
自動化測試的價值
- 回歸保護:修改 A 功能時,自動檢測 B、C、D 功能是否被影響
- 重構信心:有測試覆蓋的程式碼,重構時心裡有底
- 活文件:好的測試就是最好的使用說明書
- 快速回饋:幾秒鐘內知道程式碼是否正確,而不是等到部署後才發現問題
1. 測試金字塔:測試的層次與比例
1.1 三層金字塔
Mike Cohn 提出的測試金字塔是測試策略的經典模型。它告訴我們:不同類型的測試應該有不同的數量比例。
透過下面的互動元件,點擊金字塔的每一層,了解各層測試的特點:
1.2 為什麼是金字塔形?
金字塔形狀反映了一個核心權衡:速度與真實度的取捨。
- 底層(單元測試):速度極快、數量最多、成本最低,但只能驗證單個零件
- 中層(整合測試):速度適中、數量適中,驗證零件之間的配合
- 頂層(E2E 測試):最接近真實使用者,但速度慢、維護成本高、容易因環境問題失敗
反面模式:冰淇淋甜筒 —— 如果你的專案 E2E 測試最多、單元測試最少,那就是倒過來的「冰淇淋甜筒」。這意味著測試套件執行緩慢、經常失敗、維護成本極高。
2. 單元測試實戰
2.1 什麼是好的單元測試?
好的單元測試遵循 FIRST 原則:
| 原則 | 含義 | 說明 |
|---|---|---|
| Fast | 快速 | 毫秒級完成,開發者願意頻繁執行 |
| Independent | 獨立 | 測試之間互不依賴,可以單獨執行 |
| Repeatable | 可重複 | 任何環境下執行結果一致 |
| Self-validating | 自驗證 | 結果是明確的通過/失敗,不需要人工判斷 |
| Timely | 及時 | 在寫程式碼的同時(或之前)寫測試 |
2.2 測試的結構:AAA 模式
每個測試都應該有清晰的三段式結構:
test('應該正確計算含稅價格', () => {
// Arrange(準備)—— 設定測試資料
const price = 100
const taxRate = 0.13
// Act(執行)—— 呼叫被測函式
const result = calculateTotalWithTax(price, taxRate)
// Assert(斷言)—— 驗證結果
expect(result).toBe(113)
})2.3 測試什麼?不測什麼?
應該測試的:
- 核心業務邏輯(價格計算、權限判斷、資料轉換)
- 邊界條件(空值、零、負數、超大數)
- 錯誤處理路徑
不需要測試的:
- 第三方函式庫的內部實作
- 簡單的 getter/setter
- 框架自身的功能(如 Vue 的響應式系統)
3. TDD:測試驅動開發
3.1 紅綠重構循環
TDD(Test-Driven Development)的核心是一個簡單的循環:先寫測試,再寫實作,最後重構。
透過下面的互動元件,親自體驗 TDD 的完整循環:
test('add(1, 2) should return 3', () => {
expect(add(1, 2)).toBe(3)
})3.2 TDD 的三條規則
- 不寫任何產品程式碼,除非是為了讓一個失敗的測試通過
- 只寫剛好讓測試失敗的測試程式碼(編譯不過也算失敗)
- 只寫剛好讓測試通過的產品程式碼
3.3 TDD 的真正價值
TDD 的價值不僅在於「先寫測試」,更在於它迫使你思考介面設計。當你先寫測試時,你是站在「使用者」的角度思考:這個函式應該接收什麼參數?回傳什麼結果?這自然會導向更好的 API 設計。
TDD 不是銀彈
TDD 適合邏輯密集的程式碼(演算法、業務規則、資料轉換),但對於 UI 佈局、探索性原型等場景,強制 TDD 反而會拖慢速度。關鍵是理解它的思想,靈活運用。
4. 測試策略選擇
4.1 不同專案的測試重點
| 專案類型 | 測試重點 | 推薦比例 |
|---|---|---|
| 工具庫/SDK | 單元測試為主 | 90% 單元 + 10% 整合 |
| API 服務 | 整合測試為主 | 30% 單元 + 60% 整合 + 10% E2E |
| Web 應用 | 均衡分布 | 50% 單元 + 30% 整合 + 20% E2E |
| MVP/原型 | 關鍵路徑 E2E | 少量核心測試即可 |
4.2 常用測試工具
| 工具 | 類型 | 適用場景 |
|---|---|---|
| Vitest | 單元/整合 | Vite 專案首選,相容 Jest API |
| Jest | 單元/整合 | Node.js 生態最受歡迎 |
| Playwright | E2E | 跨瀏覽器,微軟出品 |
| Cypress | E2E | 開發體驗好,除錯方便 |
| Testing Library | 元件測試 | 以使用者視角測試 UI 元件 |
5. AI 助力:用大模型提升測試效率
大模型在測試領域的能力已經非常強大——它可以幫你產生測試案例、發現邊界條件、甚至寫出完整的測試程式碼。
5.1 產生單元測試
提示詞:
請為以下函式撰寫單元測試,使用 Vitest 框架,要求: 1. 遵循 AAA 模式(Arrange-Act-Assert) 2. 覆蓋正常路徑、邊界條件和錯誤路徑 3. 每個測試案例有清楚的描述 [貼上你的函式程式碼]
5.2 發現邊界條件
提示詞:
分析以下函式,列出所有可能的邊界條件和極端輸入場景, 包括:空值、零、負數、超大數、特殊字元、並行情況等。 對每個場景說明預期行為和可能的風險。 [貼上你的函式程式碼]
5.3 從需求產生測試(TDD 輔助)
提示詞:
我要實作一個購物車模組,需求如下: - 添加商品、刪除商品、修改數量 - 自動計算總價(含折扣) - 庫存不足時提示錯誤 請按照 TDD 思路,先寫出測試案例(不寫實作), 使用 Vitest,覆蓋所有核心場景。
AI 使用建議
AI 產生的測試要檢查斷言是否有意義——避免 expect(true).toBe(true) 這種無效測試。好的測試應該在程式碼出錯時真的能失敗。
6. 總結
- 測試金字塔:底層多、頂層少,平衡速度與真實度
- 單元測試:遵循 FIRST 原則和 AAA 模式,測試核心邏輯
- TDD:紅綠重構循環,用測試驅動設計
- 策略選擇:根據專案類型和階段,選擇合適的測試比例
終極思考
測試不是負擔,而是加速器。短期看,寫測試確實多花了時間;長期看,它節省了無數次手動驗證、排查回歸 Bug、以及深夜緊急修復的時間。好的測試讓你有信心說出那句話:「放心改,測試會告訴我們有沒有問題。」
延伸閱讀
- 經典書籍:Kent Beck《測試驅動開發》是 TDD 的開山之作。
- 實用指南:嘗試用 Vitest 為一個小專案寫測試,體驗從零開始的測試流程。
- 測試模式:了解 Mock、Stub、Spy 的區別和使用場景。
- 持續整合:將測試整合到 CI/CD 流水線中,每次提交自動執行。