TDD(測試驅動開發)不僅產出完整單元測試套件,更能將設計決策推遲至「最後負責時刻」,透過測試驅動代碼結構,產生更簡單、可組合、可重用的設計。本文以「完全數查找器」為例,對比「測後開發」與 TDD 兩種流程的設計差異。
關鍵差異:「測後開發」先寫代碼再測試,思維已鎖定實作細節;TDD 先寫測試,強迫從使用者視角思考 API 設計,再反推實作。
首版 PerfectNumberFinder1 一次性實作所有邏輯:
public static boolean isPerfect(int number) {
List<Integer> factors = new ArrayList<>();
factors.add(1); factors.add(number);
for (int i = 2; i < number; i++) if (number % i == 0) factors.add(i);
int sum = 0; for (int n : factors) sum += n;
return sum - number == number;
}
問題顯而易見:
ArrayList 存因子,實則因子是集合非列表(無序、不重複)測試只能驗證輸入輸出,無法察覺內部結構缺陷。
從最簡單問題切入:「判斷數字是否為因子」
@Test public void is_1_a_factor_of_10() { assertTrue(Classifier1.isFactor(1, 10)); }
極簡實作:
public static boolean isFactor(int factor, int number) { return number % factor == 0; }
下一步:「獲取因子列表」,測試驅動 API 設計:
@Test public void factors_for_6() {
int[] expected = {1, 2, 3, 6};
Classifier2 c = new Classifier2(6);
assertThat(c.getFactors(), is(expected));
}
初版實作暴露問題:靜態方法導致參數傳遞繁瑣,轉向實例化設計,將 _number 封裝為欄位。
測試 addFactor() 時失敗:
@Test public void add_factors() {
Classifier3 c = new Classifier3(6);
c.addFactor(2); c.addFactor(3);
assertThat(c.getFactors(), is(Arrays.asList(1, 2, 3, 6))); // 期望有序
}
實際返回 [1, 6, 2, 3]——因建構子預先加入 1 和自身。根因在於:因子本質是集合,而非列表。 改用 Set 瞬時解決順序與去重問題,設計品質躍升。
這正是 TDD 價值:在代碼固化前,測試逼迫你正視領域模型的真實面貌。
重構將 getFactors() 拆解為:
_factors(含 1、自身)calculateFactors() 迴圈尋因子addFactor() 單一職責添加因子Set每個方法單一職責、同一抽象層級,符合 Kent Beck 組合方法模式。
「測後」版本將測試視為驗收門檻;TDD 將測試視為設計探針。下篇將完成 TDD 版本,並以圈複雜度量化優勢,同時探討私有方法測試策略、邊界條件系統性覆蓋等進階主題。