代码覆盖(Code Coverage)为何物?相信程序员特别是测试人员不陌生,很多人都喜欢用代码覆盖来驱动测试的开展和完善。确实代码覆盖可以找出测试疏漏和代码问题,但是单纯的代码覆盖率高低并不能直接反映代码质量的好坏。大多我们的努力方向都是找出那些没有覆盖到的代码,然后补充用例,完善测试。而摆在我们面前的问题是:是否我们已经充分认识到哪些不需要、不能、必须被覆盖?只有对代码覆盖的各种情景了然于胸,才能不盲目乐观于代码覆盖率之高,悲观于代码覆盖率之低。在实践中(本文面向主要Java语言,基于emma工具),梳理可知,对于代码覆盖我们可能都会遇到以下15种典型情景:

  1、代码覆盖

  即代码所有路径被经过,这种需要注意的是:不应该覆盖而被覆盖的情况。例如某种特殊异常是不期望遇到的,但是遇到了,异常处理的代码也覆盖了,这时,我们应该追溯异常产生的根本原因,而不因覆盖了直接忽略。

  提示:不仅要关注未覆盖的代码,也要关注覆盖的,特别是偶然覆盖的代码。

  2、废弃的功能

  一些功能点随着产品版本不断更新,可能会被取消,这部分功能可以直接移除,保留只会让代码看起来越冗余。如果某天需要参考或找回删除的那些代码,CVS/SVN工具搞定了。

  提示:不用的功能不需要覆盖,要及时删除,不通过保留或者注释的方式残留在代码中。

  3、工具类(助手类)、常量类等的私有构造器

  工具类和常量类共有的特征是对外开放的都是静态方法,调用方法的时候,无需创建实例,所以推荐实践是创建一个private的构造器方法。这导致类的构造器代码无法覆盖(不考虑反射等方式)。

  相反,如果某天发现对于这样的类覆盖率为,那检查下是否代码写的不规范:用默认构造器,然后通过实例来调用静态方法。

  例1:工具类

public final class StringUtil {

    public static String concatWithSpace(String... strings) {
        return concat(MarkConstants.SPACE, strings);
    }


    public static String concatWithSemicolon(String... strings) {
        return concat(MarkConstants.SEMICOLON, strings);
    }

    private StringUtil() {
    }

}

  例2:常量类

public final class MarkConstants {
    /**
    *{@value}
     */
    public static final String SEMICOLON = ";";

    private MarkConstants() {
    }

}

  提示:工具类(助手类)、常量类等的私有构造器不能被覆盖

  4、日志级别配置

  日志级别不同,覆盖率高低也不同。在产品部署中,很少将日志的级别设成debug,因为日志占用磁盘空间会增长很快。只在做一些问题跟踪、调试时才会调高日志级别。

  所以环境使用不同的日志级别,也会导致一些日志代码没有覆盖。如以下示例程序,不打开debug级别无法覆盖部分代码:

public static String formatPath(String path) {
     ValidationUtil.checkString(path);
     String returnPath = path.trim();
     if (!returnPath.startsWith(SPLIT))
         returnPath = SPLIT + returnPath;
     if (returnPath.endsWith(SPLIT))
         returnPath = returnPath.substring(0, returnPath.length() - 1);

     if (LOGGER.isDebugEnabled())
         LOGGER.debug(String
                 .format("[util]convert [%s] to [%s]", path, returnPath));
     return returnPath;
}