TDD三定律
1、在編寫不能通過的單元測試前,不可編寫生產(chǎn)代碼。
2、只可編寫剛好無法通過的單元測試,不能編譯也算不通過。
3、只可編寫剛好足以通過當前失敗測試的生產(chǎn)代碼。
TDD的關鍵步驟
- 添加一個小的測試
- 運行所有測試并且失敗
- 做一點修改
- 運行所有測試并且成功
- 重構以消除重復
基于這個思想,上codewars找一道題實踐一把tdd。
ATM machines allow 4 or 6 digit PIN codes and PIN codes cannot contain anything but exactly 4 digits or exactly 6 digits.
If the function is passed a valid PIN string, return true, else return false.
eg:
Solution.validatePin("1234") === true
Solution.validatePin("12345") === false
Solution.validatePin("a234") === false
根據(jù)題目來看,寫一個方法,輸入是一個密碼,返回一個boolean值。根據(jù)tdd關鍵步驟,先添加一個測試。
codewars已經(jīng)有示例了。
@Test
public void validPins() {
assertEquals(true, Solution.validatePin("1234"));
assertEquals(true, Solution.validatePin("0000"));
assertEquals(true, Solution.validatePin("1111"));
assertEquals(true, Solution.validatePin("123456"));
assertEquals(true, Solution.validatePin("098765"));
assertEquals(true, Solution.validatePin("000000"));
assertEquals(true, Solution.validatePin("090909"));
}
接下來到第二步,運行所有測試并失敗。很顯然會失敗,因為Solution就沒有這個類,這里不運行,先創(chuàng)建solution類。(第三步做一點修改)
public class Solution {
}
good news Solution不報錯了。但是validatePin報錯了,因為還沒有這個方法。運行測試,失敗了。哦哦,來繼續(xù)做一點修改以通過測試。添加validatePin方法。
public static boolean validatePin(String pin){
return true;
}
ok不報錯了。
我們看第一個測試方法validPins,全部驗證的是true。那我們直接返回true,運行測試。

測試成功了。到此結束了嗎?好像沒有,我們的測試用例沒有覆蓋到需求。還需要寫更多的測試用例以滿足需求。看看需求:四位或六位的數(shù)字。添加測試:
@Test
public void nonDigitCharacters() {
assertEquals(false, Solution.validatePin("a234"));
assertEquals(false, Solution.validatePin(".234"));
}
@Test
public void invalidLengths() {
assertEquals(false, Solution.validatePin("1"));
assertEquals(false, Solution.validatePin("12"));
assertEquals(false, Solution.validatePin("123"));
assertEquals(false, Solution.validatePin("12345"));
assertEquals(false, Solution.validatePin("1234567"));
assertEquals(false, Solution.validatePin("-1234"));
assertEquals(false, Solution.validatePin("1.234"));
assertEquals(false, Solution.validatePin("00000000"));
}
ok,這里添加了2個測試方法,一個是測試非數(shù)字的,一個是測試位數(shù)的。重復tdd步驟,運行測試。

2個失敗了,1個通過了。做一點小修改,以通過測試用例。當輸入“a234”的時候或者“.234”的時候,返回false。修改方法validatePin
public static boolean validatePin(String pin){
if("a234".equals(pin) || ".234".equals(pin)){
return false;
}
return true;
}
這里因為練習pdd的步驟,所以每一步嚴格按照tdd的步驟走了,所以看起來這里的代碼比較蠢。
ok運行測試

棒棒的,我們嚴格滿足了定律3,只編寫剛好通過測試用例的代碼。ok,暫時搞定2個測試用例了,我們進行下一步。位數(shù)測試,修改一點點代碼滿足測試3
public static boolean validatePin(String pin){
if("a234".equals(pin) || ".234".equals(pin)){
return false;
}
if(pin.length()!=4 && pin.length()!=6){
return false;
}
return true;
}
運行測試

全通過了,進行下一步,重構以消除重復。
現(xiàn)在的方法看起來是這樣
public static boolean validatePin(String pin){
if("a234".equals(pin) || ".234".equals(pin)){
return false;
}
if(pin.length()!=4 && pin.length()!=6){
return false;
}
return true;
}
看到問題了,里面有硬編碼,所以我們要消除硬編碼。這段硬編碼我們是為了滿足測試2,測試2是為了測試是不是純數(shù)字,做一點小修改
public static boolean validatePin(String pin){
Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$");
Matcher isNum = pattern.matcher(pin);
if (!isNum.matches()) {
return false;
}
if(pin.length()!=4 && pin.length()!=6){
return false;
}
return true;
}
運行測試,ok還是通過了。
下一步,重構以消除重復。這個方法細分來看,做了2件事,第一是判斷是否包含數(shù)字之外的字符,第二是判斷位數(shù),那么把這兩個事可以單獨提出來提煉一個方法。重構如下
public class Solution {
public static boolean validatePin(String pin){
return validateNonDigitCharacters(pin) && invalidLengths(pin);
}
private static boolean validateNonDigitCharacters(String pin){
Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$");
Matcher isNum = pattern.matcher(pin);
return isNum.matches();
}
private static boolean invalidLengths(String pin){
return !(pin.length()!=4 && pin.length()!=6);
}
}
invalidLengths方法似乎還有些不是很能一眼看懂,不過仔細看看還是能懂,guess what,我不改了。
運行測試

測試通過了,到此結束。
總結
我把tdd理解為 邏輯代碼未動,測試代碼先行。通過不停的滿足覆蓋全面的測試代碼,一點點修改邏輯代碼,直到滿足所有測試。
TODO
可以在原需求基礎增加一個需求,繼續(xù)迭代開發(fā),體現(xiàn)出tdd的優(yōu)勢
bug
測試用例沒有覆蓋完全,沒有考慮空值
最優(yōu)解
public static boolean validatePin(String pin) {
return pin.matches("\\d{4}|\\d{6}");
}