记录和分享一个经典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驱动这些方法?陷入两难选择。
- 如果将这组方法作为一个私有方法,但是没法很好的支持测试。
- 如果当作公共接口去调用,问题是如果出错,定位不准去。
- 测试的细节穿透力不强
通过共有接口或者方法,去测试后面的一个私有方法. 私有方法逻辑简单问题不大, 如果这个私有方法比较复杂,就有隔靴搔痒。没有直接测试私有方法那么给力。
- 如果将这些方法,作为一个共有的方法。
- 暴露出DiamondPrint 类的内部实现部分。
- 第二使得不同层次的api暴露出来,抽象不统一,接口可读性差。
- 另外这些方法测测试与基于需求的测试用例混合在一起,是的需求不清晰。因为这个不是同一个层面的抽象放在一起,可读性就会差很多。
@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来开发。然后在上面的类里面调用已经测试好的方法。也就是从下往上开发。好处也很明显。
- 直接测试,给力;
- 测试反馈定位准确;
- 测试代码可读性提升;
- API的抽象层次统一。
其实还有很有争议的两个方法。
-
test recycle。 直接修改测试用例,使得代码和测试用例渐进演化,最终实现。自己感觉不推荐,中间的测试用例被删除了,使得测试用例分解过程,以及如果后期代码维护出错反馈定位不准确。看代码 和文章。
-
与3 相反,测试自顶向下;对于没有实现的部分,先用hardcode,然后再逐步用代码替换一部分; 这个其实思路和1类似,是基于对外的接口来测试私有方法。缺点同上,中间测试用例没有,同样后期维护不方便。
回到开始问题: 私有方法测试还是不测试?
简单回答,NO! 私有方法要通过对外的接口来测试;如果私有方法复杂,提取出工具类去测试。
Note: 这个题目有很多种解法,下一篇来介绍。
网友评论