記錄和分享一個經典kata, 題目是如何打印一個鉆石型的字符。
Given a letter print a diamond starting with 'A'
with the supplied letter at the widest point.
For example: print-diamond 'E' prints
A
B B
C C
D D
E E
D D
C C
B B
A
按照TDD的套路,從最簡單的開始,第一個開始 'A'。
@Test
public void Print_A_Test() {
String[] expected = {"A"};
String[] actual = DimondPrint.print('A');
diamodAssert(expected, actual);
}
public static String[] print( char val) {
return new String[] {"A"};
}
接著第二個用例如下:
@Test
public void Print_B_Test() {
String[] expected = {
" A ",
"B B",
" A "
};
String[] actual = DimondPrint.print('B');
diamodAssert(expected, actual);
}
public static String[] print( char val) {
if( val == 'B') {
return new String[] {
" A ",
"B B",
" A "
};
}
return new String[] {"A"};
}
第三個測試用例就是,測試字符'C'
@Test
public void Print_C_Test() {
String[] expected = {
" A ",
" B B ",
"C C",
" B B ",
" A "
};
String[] actual = DimondPrint.print('C');
diamodAssert(expected, actual);
}
當時在做到這個時候,明顯感覺步伐太大,需要小步分解。
第一個思路就是可以將這個菱形分解成四個小正方形, 左上,右上,左下,右下,然后兩兩合并。這個就有一組方法.
String[] getLeftTopSquare(char c){...}
String[] getRightTopSquare(char c){...}
String[] getRightDownSquare(char c){...}
String[] getLeftDownSquare(char c){...}
String[] mergeOnVertical(String[] top, String[] down){...}
String[] mergeOnHorizontal(String[] top, String[] down){...}
但是面臨的問題是:
如何用TDD驅動這些方法?陷入兩難選擇。
- 如果將這組方法作為一個私有方法,但是沒法很好的支持測試。
- 如果當作公共接口去調用,問題是如果出錯,定位不準去。
- 測試的細節(jié)穿透力不強
通過共有接口或者方法,去測試后面的一個私有方法. 私有方法邏輯簡單問題不大, 如果這個私有方法比較復雜,就有隔靴搔癢。沒有直接測試私有方法那么給力。

直接測試 vs 間接測試
- 如果將這些方法,作為一個共有的方法。
- 暴露出DiamondPrint 類的內部實現(xiàn)部分。
- 第二使得不同層次的api暴露出來,抽象不統(tǒng)一,接口可讀性差。
- 另外這些方法測測試與基于需求的測試用例混合在一起,是的需求不清晰。因為這個不是同一個層面的抽象放在一起,可讀性就會差很多。
@Test
public void Print_A_Test() {
String[] expected = {"A"};
String[] actual = DimondPrint.print('A');
diamodAssert(expected, actual);
}
@Test
public void Print_mergeOnVertical_Test() {
String[] expected = {"A","B"};
String[] actual = DimondPrint.mergeOnVertical("A", "B");
diamodAssert(expected, actual);
}
- 提取工具類。
將上面一組方法提取到一個抽象類,比如DiamondUtility; 然后對這個類用TDD來開發(fā)。然后在上面的類里面調用已經測試好的方法。也就是從下往上開發(fā)。好處也很明顯。
- 直接測試,給力;
- 測試反饋定位準確;
- 測試代碼可讀性提升;
- API的抽象層次統(tǒng)一。
其實還有很有爭議的兩個方法。
test recycle。 直接修改測試用例,使得代碼和測試用例漸進演化,最終實現(xiàn)。自己感覺不推薦,中間的測試用例被刪除了,使得測試用例分解過程,以及如果后期代碼維護出錯反饋定位不準確???a target="_blank" rel="nofollow">代碼 和文章。
與3 相反,測試自頂向下;對于沒有實現(xiàn)的部分,先用hardcode,然后再逐步用代碼替換一部分; 這個其實思路和1類似,是基于對外的接口來測試私有方法。缺點同上,中間測試用例沒有,同樣后期維護不方便。
回到開始問題: 私有方法測試還是不測試?
簡單回答,NO! 私有方法要通過對外的接口來測試;如果私有方法復雜,提取出工具類去測試。
Note: 這個題目有很多種解法,下一篇來介紹。