01、前言
很早之前,曾在網絡上見到過 TDD 這 3 個大寫的英文字母,它是 Test Driven Development 這三個單詞的縮寫,也就是“測試驅動開發”的意思——聽起來很不錯的一種理念。
其理念主要是確保兩件事:
- 確保所有的需求都能被照顧到。
- 在代碼不斷增加和重構的過程中,可以檢查所有的功能是否正確。
但後來很長一段時間裏,都沒再聽過 TDD 的消息。有人說,TDD 已經死了,給出的意見如下:
1)通常來說,開發人員不應該在沒有失敗的測試用例下編寫代碼——這似乎是合理的,但是它可能導致過度測試。例如,爲了保證一行生産代碼的正確性,你不由得寫了 4 行測試代碼,這意味著一旦這一行生産代碼需要修改,你也得修改那 4 行測試代碼。
2)爲了遵循 TDD 而寫的代碼,容易進入一個誤區:代碼是爲了滿足測試用的,而忽略了實際需求。
02、TDD 到底是什麽?
不管 TDD 到底死了沒有,先讓我們來回顧一下 TDD 到底是什麽。
TDD 的基本思想就是在開發功能代碼之前,先編寫測試代碼。也就是說在明確要開發某個功能後,首先思考如何對這個功能進行測試,並完成測試代碼的編寫,然後編寫相關的代碼滿足這些測試用例。然後循環進行添加其他功能,直到完全部功能的開發。
TDD 的基本過程可以拆解爲以下 6 個步驟:
1) 分析需求,把需求拆分爲具體的任務。
2) 從任務列表中取出一個任務,並對其編寫測試用例。
3) 由于沒有實際的功能代碼,測試代碼不大可能會通過(紅)。
4) 編寫對應的功能代碼,盡快讓測試代碼通過(綠)。
5) 對代碼進行重構,並保證測試通過(重構)。
6) 重複以上步驟。
可以用下圖來表示上述過程。
那接下來,王二需要快速讓測試通過,Ticket.sale() 方法修改後的結果如下:
public class Ticket {
public BigDecimal sale(int count) { if (count == 1) { return new BigDecimal(“99”); } return BigDecimal.ZERO; }
}
再運行一下測試用例,結果如下圖所示,綠色表示測試通過了:預期結果是 99,實際結果是 99。
有兩個測試用例沒有通過,那麽王二需要繼續修改功能代碼,調整如下:
public class Ticket { private final static int PRICE = 99;
public BigDecimal sale(int count) { if (count < 0) { throw new IllegalArgumentException(“銷量不能爲負數”); } if (count == 0) { return BigDecimal.ZERO; }
if (count == 1) { return new BigDecimal(PRICE); }
return new BigDecimal(PRICE * count); }
}
再運行一下測試用例,發現都通過了。又到了重構的時候了,銷量爲零、或者大于等于一的時候,代碼可以合並,于是重構結果如下:
public class Ticket { private final static int PRICE = 99;
public BigDecimal sale(int count) { if (count < 0) { throw new IllegalArgumentException(“銷量不能爲負數”); } return new BigDecimal(PRICE * count); }
}
重構結束後,再運行測試用例,確保重構後的代碼依然可用。
04、最後
從上面的實踐過程可以得出如下結論:
TDD 想要做的就是讓我們對自己的代碼充滿信心,因爲我們可以通過測試代碼來判斷這段代碼是否正確無誤。
也就是說,TDD 流程比較關鍵的一環在于如何寫出有效的測試代碼,這裏有 4 個原則可以參考:
1)測試過程應該盡量模擬正常使用的過程。
2)應該盡量做到分支覆蓋。
3)測試數據應該盡量包括真實數據,以及邊界數據。
4)測試語句和測試數據應該盡量簡單,容易理解。
注意,這 4 個原則不僅適用于 TDD,同樣適用于任何流程下的單元測試。

