TDD私有方法测还是不测

记录和分享一个经典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驱动这些方法?陷入两难选择。

  1. 如果将这组方法作为一个私有方法,但是没法很好的支持测试。
  • 如果当作公共接口去调用,问题是如果出错,定位不准去。
  • 测试的细节穿透力不强
    通过共有接口或者方法,去测试后面的一个私有方法. 私有方法逻辑简单问题不大, 如果这个私有方法比较复杂,就有隔靴搔痒。没有直接测试私有方法那么给力。
直接测试 vs 间接测试
  1. 如果将这些方法,作为一个共有的方法。
  • 暴露出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);
   }

  1. 提取工具类。

将上面一组方法提取到一个抽象类,比如DiamondUtility; 然后对这个类用TDD来开发。然后在上面的类里面调用已经测试好的方法。也就是从下往上开发。好处也很明显。

  • 直接测试,给力;
  • 测试反馈定位准确;
  • 测试代码可读性提升;
  • API的抽象层次统一。

其实还有很有争议的两个方法。

  1. test recycle。 直接修改测试用例,使得代码和测试用例渐进演化,最终实现。自己感觉不推荐,中间的测试用例被删除了,使得测试用例分解过程,以及如果后期代码维护出错反馈定位不准确。看代码文章

  2. 与3 相反,测试自顶向下;对于没有实现的部分,先用hardcode,然后再逐步用代码替换一部分; 这个其实思路和1类似,是基于对外的接口来测试私有方法。缺点同上,中间测试用例没有,同样后期维护不方便。

回到开始问题: 私有方法测试还是不测试?

简单回答,NO! 私有方法要通过对外的接口来测试;如果私有方法复杂,提取出工具类去测试。

Note: 这个题目有很多种解法,下一篇来介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,478评论 19 139
  • 文| 麻生生 儒家和道家,都是中国文化的主流。儒家塑造着国人的社会人格,道家则成就着国人的自然人格。道家的智慧,博...
    _Gloria_阅读 4,276评论 16 6
  • 我们每个人都是普通人而已。 还记得小的时候,还是孩童的我总是骑在父亲的头上玩耍,嬉闹。有什么委屈...
    唱歌的小孩纸阅读 1,917评论 3 2
  • 冬去春来, 花落花开, 不知道啊, 多少鲜花等待此时, 不明白啊, 多少鲜花为此开, 花瓣飞, 飞跃,飞扬,飞飏。
    励志决定成为漫画家的人阅读 1,209评论 0 2
  • 昨天是我唯一没有下游戏币的比赛,之前无论什么比赛我都会选择一方,对比赛会有个整体的预测。也猜中了不少冷门,比如冰岛...
    流殇夕水阅读 2,523评论 0 0