首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

演化架構與緊急設計:測試驅動設計(下)——測試品質、邊界條件與程式碼度量

     編輯:Java 相關知識

承接上篇,我們以「完全數查找器」為例,深入探討 TDD 如何透過測試品質把關、邊界條件探勘、肯定/否定測試雙重驗證、效能重構,最終以圈複雜度量化程式碼優勢。

測試品質:保持「適度濕潤」

DRY 原則(Don't Repeat Yourself)在產品代碼中至關重要,但單元測試天生需要相似結構驗證不同場景。Andy Hunt 與 Dave Thomas 指出:測試應當「濕潤但不濕透」——允許必要重複,拒絕笨拙結構。

原始測試構造預期集合冗長:

Set<Integer> expected = new HashSet<>(Arrays.asList(1, 2, 3, 6));

提煉私有輔助方法:

private Set<Integer> expectationSetWith(Integer... numbers) {
    return new HashSet<>(Arrays.asList(numbers));
}

測試瞬變清爽:

@Test public void factors_for_6() {
    Set<Integer> expected = expectationSetWith(1, 2, 3, 6);
    Classifier4 c = new Classifier4(6);
    c.calculateFactors();
    assertThat(c.getFactors(), is(expected));
}

良好的測試代碼同樣遵循設計原則,輔助方法消除樣板雜訊,聚焦業務意圖。

邊界條件:需求收集的「有損壓縮」

需求文檔如同有損壓縮圖片,細節必然流失。開發者必須透過測試邊界條件「解壓」隱性假設:

@Test public void factors_for_100() { ... } // 多因子數字
@Test(expected = InvalidNumberException.class)
public void cannot_classify_negative_numbers() { new Classifier5(-20); } // 負數無意義
@Test public void factors_for_max_int() { ... } // 整數上限驗證

測試負數發現領域模型缺陷,測試 Integer.MAX_VALUE 反推長整數支援需求。每個邊界測試都是與業務方對話的契機,將模糊需求轉化為確定性代碼契約。

肯定與否定測試雙重保障

已知完全數 {6, 28, 496, 8128, 33550336} 編寫肯定測試:

@Test public void perfection() {
    int[] perfectNumbers = {6, 28, 496, 8128, 33550336};
    for (int n : perfectNumbers) assertTrue(classifierFor(n).isPerfect());
}

但否定測試同樣關鍵——確保非完全數不被誤判:

@Test public void test_a_bunch_of_numbers() {
    Set<Integer> perfectSet = new HashSet<>(Arrays.asList(PERFECT_NUMS));
    for (int i = 2; i < 33550340; i++) {
        if (perfectSet.contains(i)) assertTrue(classifierFor(i).isPerfect());
        else assertFalse(classifierFor(i).isPerfect());
    }
}

否定測試雖慢,卻暴露演算法缺陷:原始 calculateFactors() 循環至 _number 導致 O(n) 複雜度。

效能重構:先正確、後快速

成對尋找因子,循環至平方根:

public void calculateFactors() {
    for (int i = 2; i < Math.sqrt(_number) + 1; i++)
        if (isFactor(i)) addFactor(i);
}
private void addFactor(int factor) {
    _factors.add(factor);
    _factors.add(_number / factor); // 成對加入
}

因使用 Set 自動去重,避免了「測後」版本中平方數重複因子的防護邏輯。完整單元測試套件讓重構無畏——跑測試、綠燈、提交,循環往複。

可組合方法:組合方法模式的實踐

Kent Beck《Smalltalk Best Practice Patterns》提倡:將程式分解為多個單一職責小方法,保持同一抽象層級。TDD 自然產出此結構:

isFactor() → getFactors() → calculateFactors() → addFactor() → sumOfFactors() → isPerfect()

當新需求「判斷盈數/虧數」到來:

public boolean isAbundant() { calculateFactors(); return sumOfFactors() - _number > _number; }
public boolean isDeficient() { calculateFactors(); return sumOfFactors() - _number < _number; }

僅增兩行邏輯,複用現有構建塊;而「測後」版本需重寫整個單體方法。可組合性直接轉化為維護效率。

圈複雜度量化:TDD 版本簡單三倍

使用 JavaNCSS 測量:

  • 測後版本:單一方法,平均 13 行,圈複雜度 5.00
  • TDD 版本:7 個方法,平均 3.56 行,平均圈複雜度 1.56

結論:TDD 透過微小步進、持續重構、測試保護,天然引導出低複雜度、高內聚、可組合的設計。這不是偶然,而是習慣使然。

下一步預告

下篇將暫別測試,探討從 Smalltalk 借鑑的兩大模式:組合方法與單一抽象層級原則(SLAP),為代碼結構提供整體導航目標。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved