5.2 單元測試與 TDD#
什麼是單元測試#
單元測試(Unit Test) 是測試金字塔的基座:針對軟體的最小可測單位——通常是單一函式或單一類別的單一方法——撰寫測試,驗證它的行為。
單元測試的關鍵特徵是隔離(Isolation):測試一個單元時,把它與外部依賴(資料庫、網路、時間、檔案系統)隔離,只關注這個單元本身的邏輯。隔離靠 Mock / Stub(模擬) 來達成——用「假物件」代替真實的外部依賴。
# 一個待測的單元
def apply_discount(price, discount_rate):
if discount_rate < 0 or discount_rate > 1:
raise ValueError("折扣率必須在 0 到 1 之間")
return round(price * (1 - discount_rate), 2)
# 對應的單元測試(pytest 風格)
def test_apply_discount_normal():
assert apply_discount(100, 0.1) == 90.0
def test_apply_discount_full():
assert apply_discount(100, 1.0) == 0.0
def test_apply_discount_invalid_rate():
with pytest.raises(ValueError):
apply_discount(100, 1.5) # 邊界條件(2.4 節)
def test_apply_discount_rounding():
assert apply_discount(10, 0.1) == 9.0
注意上例把 2.4 節的邊界條件直接變成了測試——這正是「邊界條件 + 測試」的完美結合。
單元測試的價值#
- 快速反饋:毫秒級跑完,讓你在「寫完就測」的循環中及早抓錯
- 精準定位:失敗時能立刻知道「哪個函式、哪個邏輯錯了」
- 重構的安全網(4.2 節):改結構時確保行為不變
- 可執行的文件:測試描述了「這個函式應該怎麼表現」
- 品質護欄:在 CI 中自動執行,防止迴歸
TDD:測試驅動開發#
TDD(Test-Driven Development,測試驅動開發) 是一種「先寫測試、再寫實作」的開發方法。它的循環我們在 4.2 節提過——
flowchart LR
R[Red<br/>先寫一個會失敗的測試<br/>證明需求存在] --> G[Green<br/>寫最少實作讓測試通過]
G --> F[Refactor<br/>改善結構,測試保持綠]
F --> R三步展開#
- Red(紅):先寫一個描述「需求/行為」的測試,此時還沒有實作(或實作不完整),測試失敗。這一步的意義:先明確「正確是什麼樣」。
- Green(綠):用最簡單的方式寫實作,讓測試通過。不追求完美,先「能跑」。
- Refactor(重構):在測試保護下改善結構(4.2 節),確認測試仍為綠。
TDD 的價值#
TDD 不只是「寫測試」的方法,更是一種設計思維: - 強迫你先想清楚「介面與行為」:先寫測試,就得先決定函式簽名、輸入輸出、邊界處理——這正是「以介面驅動設計」 - 確保循序漸進的小步:一次只推進一個行為,避免一次寫一大坨 - 內建安全網:每個新行為都有測試鎖定,重構與擴展更安心
TDD 與 AI 的結合(值得期待):TDD 的「先寫測試」恰好符合 4.4 節「用測試作準繩」——讓 AI「先寫測試、再讓測試通過」。這把 AI 的自主迴圈轉變成「Red→Green」的可驗證過程(詳見 5.4 節)。
覆蓋率:工具還是目標#
程式碼覆蓋率(Code Coverage)測量「有百分之幾的程式碼被測試執行到」。它是追蹤測試廣度的有用指標,但必須小心看待:
- 好處:能找出「完全沒被測到」的死角
- 陷阱:高覆蓋率 ≠ 高品質。你可以覆蓋 100% 的程式碼,卻沒斷言任何關鍵行為(只測「有執行」不測「對不對」);反而邊界的缺失(2.4 節)才是真正的 bug 來源。
正確心態:覆蓋率是參考工具,不是終極目標。重點在測試的品質(有沒有測到正確的邏輯與邊界),而非單純追求數字。「測到要緊的地方」比「測得多」重要。
如何寫出好的單元測試#
好的單元測試有幾個特徵:
- 一個測試只測一件事:命名、輸入、斷言都聚焦單一行為,失敗時最容易定位
- 命名清楚表達行為:
test_apply_discount_invalid_rate比test_1好 - 測行為而非實作:斷言「輸入→輸出」的正確性,不要過度耦合於內部實作細節(否則微調內部就大量紅燈)
- 涵蓋邊界條件:正常值、極值、空值、非法值(2.4 節)
- 獨立、可重複:每個測試不依賴其他測試的順序或狀態
- 快速:單元測試要快,慢了就沒人想跑
AI 時代的單元測試#
AI 讓「寫單元測試」變得很便宜(5.4 節會詳談),這裡先記住幾個 AI 時代的觀察:
- AI 適合生成大量慣例化的單元測試(測試一個函式的正常/邊界)
- 但 AI 生成的測試也可能是「空轉」:只斷言了「函式有執行」而沒斷言「行為正確」(高覆蓋率假象)
- 因此人仍要設計「什麼行為值得測」——尤其業務邏輯與邊界條件。測試的「意義」(為誰、測什麼)是本質性工作,AI 只是加速「動手寫」
本章小結#
- 單元測試測最小單位、隔離外部依賴(mock)、快而精確
- 價值:快速反饋、精準定位、重構安全網、可執行文件
- TDD 三步:Red(先寫失敗測試)→ Green(最少實作通過)→ Refactor(重構保綠)
- TDD 是「以介面驅動」的設計思維,且與 AI「用測試作準繩」高度契合
- 覆蓋率是參考工具,不是目標;「測到要緊處」重於「測得多」
- AI 時代:AI 加速寫測試,但「測什麼才有意義」由人設計
想一想#
- 為「訂位系統」的一個核心函式寫一組含邊界條件的單元測試(先寫測試,再寫實作,體驗 TDD)。
- 為什麼「高覆蓋率不等於高品質」?舉一個「覆蓋 100% 但沒測到重點」的例子。
- 你覺得 AI 生成的單元測試,最可能在哪裡「空轉」?如何避免?