章節概述#

本章利用上一章完成的 equals() 功能來改善測試的表達力,進而將 amount 欄位設為 private。這是一個典型的 TDD 模式:新完成的功能立即被用來改善既有的程式碼與測試。

用 equals() 改善測試#

有了 equality 之後,Dollar.times() 的測試可以更具表達力。概念上,times() 應該回傳一個值為「接收者乘以乘數」的 Dollar。但原本的測試沒有直接表達這件事:

public void testMultiplication() {
    Dollar five = new Dollar(5);
    Dollar product = five.times(2);
    assertEquals(10, product.amount);
    product = five.times(3);
    assertEquals(15, product.amount);
}

第一步:用 Dollar 比較取代整數比較#

public void testMultiplication() {
    Dollar five = new Dollar(5);
    Dollar product = five.times(2);
    assertEquals(new Dollar(10), product);
    product = five.times(3);
    assertEquals(new Dollar(15), product);
}

第二步:內聯暫存變數#

product 變數已經不再有太大幫助,可以直接內聯:

public void testMultiplication() {
    Dollar five = new Dollar(5);
    assertEquals(new Dollar(10), five.times(2));
    assertEquals(new Dollar(15), five.times(3));
}

技巧: 改寫後的測試讀起來更像是一個真理的斷言(assertion of truth),而非一連串的操作步驟。好的測試應該清楚地「說出」它在驗證什麼。

將 amount 設為 private#

經過上述修改,測試不再直接存取 product.amount,而是透過 equals() 來比較。現在只剩 Dollar 類別本身使用 amount 欄位,因此可以安心地將它設為 private:

private int amount;

待辦清單上的「Make amount private」可以劃掉了。

風險管理:測試之間的耦合#

注意: 這裡引入了一個風險——如果 equals() 的測試未能準確地驗證相等性是否正確運作,那麼 times() 的測試也可能無法準確驗證乘法是否正確。這是 TDD 中我們主動管理的風險。

我們不追求完美。透過用兩種方式表達每件事——程式碼和測試——我們希望將缺陷減少到足以讓我們有信心前進的程度。偶爾我們的推理會失誤,缺陷會溜過去。發生時,我們從中學到應該撰寫的測試,然後繼續前進。其餘時間,我們在飄揚的綠燈旗下勇敢前行。

本章小結#

本章的要點回顧:

  • 利用剛完成的功能來改善測試——用 equals() 讓測試更具表達力
  • 注意到如果兩個測試同時失敗,就無法準確定位問題
  • 儘管存在風險,仍然決定繼續推進
  • 利用被測物件的新功能來降低測試與程式碼之間的耦合