Skip to content

測試策略

前言

你的程式碼真的「沒問題」嗎? 每次改完程式碼手動點一遍看看有沒有壞——這種方式在專案小的時候還能湊合,但當程式碼量增長到幾萬行、團隊擴展到十幾人時,「手動點點看」就是一場災難。

本章帶你理解軟體測試的核心策略,從測試金字塔到 TDD,建立系統化的品質保障思維。

這篇文章會帶你學什麼?

章節內容核心概念
第 1 章測試金字塔測試的層次與比例
第 2 章單元測試實戰如何寫好一個測試
第 3 章TDD 驅動開發紅綠重構循環
第 4 章測試策略選擇不同場景的方案

學完本章,你將理解如何為專案選擇合適的測試策略,寫出有價值的測試,並透過 TDD 提升程式碼設計品質。


0. 全景圖:為什麼需要自動化測試?

想像你是一個建築工程師。每次修改圖紙後,你不會親自爬上每一層樓去檢查結構是否安全——你會依賴一套自動化的檢測系統。軟體測試就是程式碼世界的「結構檢測系統」。

自動化測試的價值

  • 回歸保護:修改 A 功能時,自動檢測 B、C、D 功能是否被影響
  • 重構信心:有測試覆蓋的程式碼,重構時心裡有底
  • 活文件:好的測試就是最好的使用說明書
  • 快速回饋:幾秒鐘內知道程式碼是否正確,而不是等到部署後才發現問題

1. 測試金字塔:測試的層次與比例

1.1 三層金字塔

Mike Cohn 提出的測試金字塔是測試策略的經典模型。它告訴我們:不同類型的測試應該有不同的數量比例

透過下面的互動元件,點擊金字塔的每一層,了解各層測試的特點:

Interactive test pyramid - click each layer for details
🖥️E2E tests
🔗Integration tests
🧪Unit tests
Higher: slower, more expensive, closer to usersLower: faster, more numerous, closer to code

1.2 為什麼是金字塔形?

金字塔形狀反映了一個核心權衡:速度與真實度的取捨

  • 底層(單元測試):速度極快、數量最多、成本最低,但只能驗證單個零件
  • 中層(整合測試):速度適中、數量適中,驗證零件之間的配合
  • 頂層(E2E 測試):最接近真實使用者,但速度慢、維護成本高、容易因環境問題失敗

反面模式:冰淇淋甜筒 —— 如果你的專案 E2E 測試最多、單元測試最少,那就是倒過來的「冰淇淋甜筒」。這意味著測試套件執行緩慢、經常失敗、維護成本極高。


2. 單元測試實戰

2.1 什麼是好的單元測試?

好的單元測試遵循 FIRST 原則:

原則含義說明
Fast快速毫秒級完成,開發者願意頻繁執行
Independent獨立測試之間互不依賴,可以單獨執行
Repeatable可重複任何環境下執行結果一致
Self-validating自驗證結果是明確的通過/失敗,不需要人工判斷
Timely及時在寫程式碼的同時(或之前)寫測試

2.2 測試的結構:AAA 模式

每個測試都應該有清晰的三段式結構:

javascript
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 的完整循環:

TDD red-green-refactor cycle - click “Next” to advance
🔴Red
🟢Green
🔵Refactor
Step 1 / 5🔴 Red - write a failing test first
Requirement: implement add(a, b). The first TDD step is not implementation, but writing a test.
add.test.js
test('add(1, 2) should return 3', () => {
  expect(add(1, 2)).toBe(3)
})
❌ Test failed - add is not defined

3.2 TDD 的三條規則

  1. 不寫任何產品程式碼,除非是為了讓一個失敗的測試通過
  2. 只寫剛好讓測試失敗的測試程式碼(編譯不過也算失敗)
  3. 只寫剛好讓測試通過的產品程式碼

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 生態最受歡迎
PlaywrightE2E跨瀏覽器,微軟出品
CypressE2E開發體驗好,除錯方便
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. 總結

  1. 測試金字塔:底層多、頂層少,平衡速度與真實度
  2. 單元測試:遵循 FIRST 原則和 AAA 模式,測試核心邏輯
  3. TDD:紅綠重構循環,用測試驅動設計
  4. 策略選擇:根據專案類型和階段,選擇合適的測試比例

終極思考

測試不是負擔,而是加速器。短期看,寫測試確實多花了時間;長期看,它節省了無數次手動驗證、排查回歸 Bug、以及深夜緊急修復的時間。好的測試讓你有信心說出那句話:「放心改,測試會告訴我們有沒有問題。」


延伸閱讀

  • 經典書籍:Kent Beck《測試驅動開發》是 TDD 的開山之作。
  • 實用指南:嘗試用 Vitest 為一個小專案寫測試,體驗從零開始的測試流程。
  • 測試模式:了解 Mock、Stub、Spy 的區別和使用場景。
  • 持續整合:將測試整合到 CI/CD 流水線中,每次提交自動執行。