本章介紹一系列關於何時寫測試、從哪裡開始寫、何時停止的模式。這些模式幫助你在 TDD 的紅燈階段做出正確的決策。
One Step Test#
問題: 待辦清單上有很多測試,下一個該挑哪一個?
解法: 選一個你有信心能實作,同時又能學到東西的測試。
每個測試應該代表朝向整體目標邁出的一步。什麼算「一步」因人而異——對新手來說的一大步,對有經驗的人可能只是十分之一步。如果清單上找不到「一步」大小的測試,就新增一些能代表漸進進展的測試。
重點: 從測試驅動長出的程式,既不是嚴格的 top-down 也不是 bottom-up,而是 known-to-unknown(從已知到未知)。我們基於現有知識出發,在開發過程中持續學習。
Starter Test#
問題: 面對全新的功能,第一個測試該怎麼寫?
解法: 從一個什麼都不做的操作變體開始測試。
如果一開始就寫出實際的測試,你會同時面對太多問題:操作屬於哪裡?正確的輸入是什麼?正確的輸出又是什麼?這會讓你太久看不到回饋。
縮短紅綠迴圈的方法是選擇極簡單的輸入和輸出。例如,面對一個多邊形化簡器(polygon reducer),Starter Test 可以是:
- 輸出等於輸入(某些多邊形已經是最簡形式)
- 輸入盡可能小,例如單一多邊形或空列表
Reducer r = new Reducer(new Polygon());
assertEquals(0, reducer.result().npoints);技巧: Starter Test 通常比後續測試更高層次,有點像應用程式層級的測試。一旦它通過了,後續測試可以在更低層次展開。
Explanation Test#
問題: 如何在團隊中推廣自動化測試?
解法: 用測試案例來請求說明和給予說明。
不要強迫別人改變工作方式。相反地,可以:
- 請求說明時用測試語言: 「讓我確認我理解你的意思——如果我有一個這樣的 Foo 和那樣的 Bar,答案應該是 76?」
- 給予說明時用測試語言: 「目前的行為是這樣:當 Foo 是這樣、Bar 是那樣,答案是 76。但如果反過來,我希望答案是 67。」
這種方式也適用於更高抽象層次,例如將 sequence diagram 轉換為測試案例。
Learning Test#
問題: 何時為外部套件撰寫測試?
解法: 在你第一次使用新 API 之前,先寫一個小測試驗證 API 行為是否符合預期。
RecordStore store;
public void setUp() {
store = RecordStore.openRecordStore("testing", true);
}
public void tearDown() {
RecordStore.deleteRecordStore("testing");
}
public void testStore() {
int id = store.addRecord(new byte[] {5, 6}, 0, 2);
assertEquals(2, store.getRecordSize(id));
byte[] buffer = new byte[2];
assertEquals(2, store.getRecord(id, buffer, 0));
assertEquals(5, buffer[0]);
assertEquals(6, buffer[1]);
}如果你對 API 的理解正確,測試第一次就會通過。
技巧: 當外部套件發佈新版本時,先跑 Learning Test。如果測試失敗,代表 API 行為有變化——在這種情況下,跑應用程式也沒意義。測試通過後,應用程式通常就能正常運作。
Another Test#
問題: 技術討論容易離題怎麼辦?
解法: 當冒出新想法時,記到測試清單上,然後回到當前主題。
程式設計偶爾需要靈感爆發,但大部分時間需要穩定推進。當你發現自己在拖延第四個待辦事項時,天馬行空的討論可能只是一種逃避方式。把新想法寫下來,帶著敬意記錄它,然後繼續手邊的工作。
Regression Test#
問題: 收到缺陷報告時第一件事做什麼?
解法: 寫出最小的、會失敗的測試,一旦修復就能通過。
迴歸測試就是「如果事先知道的話,當初就應該寫的測試」。每次寫迴歸測試時,想想當初怎樣才能預見到需要這個測試。
- 應用程式層級的迴歸測試讓使用者能具體說明問題
- 小範圍的迴歸測試幫助你改進測試策略(例如,報告中出現奇怪的大負數,提醒你未來要測試整數溢位)
有時候你需要先重構系統才能隔離缺陷——缺陷其實是系統在告訴你「設計還沒完成」。
Break#
問題: 覺得疲倦或卡住時怎麼辦?
解法: 休息一下。
喝杯水、散個步、打個盹。洗掉你對剛才做的決定和敲下的程式碼的情感依附。
往往這點距離就足以讓你突破。你可能才剛站起來就想到「我還沒試過把參數反過來!」——即使如此,還是先休息完再說。
補充: Dave Ungar 稱此為 Shower Methodology(淋浴方法論):知道該打什麼就打,不知道就去洗澡,洗到知道為止。TDD 是它的精煉版:知道就用 Obvious Implementation,不知道就 Fake It,還不清楚就 Triangulate,真的不行再去洗澡。
疲勞和判斷力之間存在負向循環:越累越無法察覺自己累了,於是繼續做下去變得更累。

Figure 26.1: Fatigue negatively affects Judgment, which negatively affects Fatigue
flowchart LR
A["😫 疲勞增加"] -->|"判斷力下降"| B["無法察覺自己累了"]
B -->|"繼續工作"| A打破這個迴圈需要引入外部因素:
| 時間尺度 | 策略 |
|---|---|
| 小時 | 桌上放水瓶,讓生理需求帶來自然的休息 |
| 天 | 下班後的約定幫助你在需要睡眠時停下 |
| 週 | 週末安排幫助你把意識從工作中抽離 |
| 年 | 強制休假政策讓你完整恢復(至少三到四週連續假期才真正有效) |
Do Over#
問題: 已經休息過了,但還是迷失方向怎麼辦?
解法: 丟掉程式碼,從頭來過。
當一小時前還很順利的程式碼現在變成一團亂,而且你想不出怎麼讓下一個測試通過時,硬撐下去通常不是好選擇。Kent Beck 在寫書過程中多次經歷這種情況,唯一一次堅持不重來的結果是丟掉了 25 頁手稿。
技巧: 如果是 pair programming,換搭檔是促成生產性重來的好方式。新搭檔沒有投入在你犯過的錯誤上,往往會溫和地拿過鍵盤說「如果我們從這裡開始呢?」
Cheap Desk, Nice Chair#
問題: TDD 的理想工作環境應該怎麼佈置?
解法: 買最好的椅子,其他傢俱可以省。
背痛會嚴重影響程式設計品質。組織願意每月花 10 萬美元在團隊上,卻不願花 1 萬美元買好椅子,這是不合理的。
作者的做法是使用便宜的折疊桌,但配最好的椅子——桌面空間充足,而且下午和早上都能保持精力充沛。
Pair programming 時也要注意舒適度:
- 清理桌面讓鍵盤可以滑動
- 打字的人應該能舒適地正對鍵盤
- 電腦硬體也適用同樣原則:個人用的機器可以便宜些,共用開發機要用最好的