代码覆盖

  1. 行覆盖
  2. 路径覆盖
  3. 数据覆盖
  4. 覆盖输出的类型
    1. 调用节点
  5. 配置覆盖工具
  6. 聚集多个测试结果报告
    1. 多个数据文件生成聚集报告
    2. 从单个数据文件的每次测试之后生成聚集报告
  7. 检测最小的覆盖
  8. 在maven项目中激活覆盖
    1. 在maven站点中包含html 报告
  9. 关闭覆盖输出
  10. 独立模式

其他的java覆盖工具包括 EMMA, Clover, Cobertura, 和 JaCoCo. (后者更经常通过 EclEmma Eclipse 插件的方式进行使用。) 这些工具提供了两种的独立的覆盖测试度量,分别是语句和分支覆盖。第一种也被叫做行覆盖,尽管他们都没有真实的尝试度量有多少的独立的可执行代码被覆盖。第二种度量有多少个可选的分支基于判断点(if 或者是 switch 语句) 在一个测试的时候被执行。JMockit覆盖使用一个不同的,但是相关的度量集合。代码覆盖包含了一系列软件度量方式,从而告诉你调有多少的生产代码被一个测试套件锁覆盖。这是相当具有度量性, 而不仅仅是说生产代码或者是测试代码的质量。也就是说,代码覆盖检测有时候能够发现那些可以删除的不可到达的代码。更重要的是,这些报告能够被当做发现缺少测试的引导。这不仅对于编写已经存在的生产代码的测试有帮助,而且对于首先编写测试,例如TDD的开发模式。(测试驱动开发)

JMockit 覆盖工具提供三种不同完备的代码覆盖度量方式:行覆盖,路径覆盖和数据覆盖。一个关于所有度量的覆盖的示例报告可以在线找到。

行覆盖

行覆盖测试度量告诉我们在一个已经被测试的源码文件中有多少执行代码被覆盖。每一个可执行的代码行可以不被覆盖,覆盖或者是部分覆盖。在第一个情况中,没有任何的可执行代码被执行。在第二个情况中,所有的代码被完整的最少执行一次。第三种情况,只有部分的部分的可执行代码行被执行。例如这通常发生在代码行中包括多个逻辑条件在复杂的条件表达式中。JMockit 覆盖工具能够标识出所有的三种情况,计算每一个可以执行行的覆盖比例:0% 表示未覆盖行, 100% 表示覆盖的行, 或者一些值在之间标识部分覆盖行。

一个分支节点存在于程序包含两个可以执行路径上。任何包含逻辑条件的代码行都能被至少分为两个可执行的段,每一个段属于一个独立的分支。一个没有包含分支节点的代码中包含一个独立段。包含一个或者多个的分支节点的代码中包含两个或者是更多可执行的代码段,在行上被连续的分支节点所分开。

在一个给定行中我们称 NS >= 1 表示可执行的代码段数目。如果 NE 使我们在测试中至少执行一次的段数目。(也就说是 他们是被覆盖的段),然后我们能够使用 100 * NE / NS来计算出被覆盖的代码段比率。

同样的,针对整个源文件的行覆盖比率可以通过计算总的可执行的段和总的被覆盖的段,考虑到所有可执行的在文件中代码行。同样的,一个包的比率可以通过总的和覆盖的在包中源码文件的覆盖段数目进行计算。最后总的代码覆盖比率可以通过使用通用的公式应用到所有的包中。

路径覆盖

一个完全不同的度量方式是路径覆盖,是通过计算方法和构造函数,而不是代码的行或者是段数目。它告诉我们多少个可能被执行的路径通过方法或构造函数在一个测试运行中至少被执行过一次,从入口到结束。

注意每一个方法或构造函数都包含一个独立的入口节点,但是却又多个退出。一个退出发生在一个返回或者是异常抛出语句。同样的包括普通的退出。一个方法或者是构造函数的执行可能被抛出一个调用异常,一个对空的引用,或者是一些其他的能够导致未可知的程序错误的情况下被终结退出。

每一个可能的路径可以被执行(覆盖)或者是不执行(覆盖)。路径可能被部分执行通过被简单的认为未覆盖(例如, 他们被终结退出)。

针对一个方法或者是构造函数体的路径覆盖比率可以通过一个简单的同行覆盖类似的计算方式获得。如果 NP是可能的通过实现体的路径,NPE 表示从开始到结束时的执行路径数目,度量可以通过 100 * NPE / NP进行计算。 与在相同的行覆盖度量中一样的,我们将这个公式拓展到所有文件,整个包中,测试运行所覆盖到的整个包集合中。

数据覆盖

通过度量有多少个实例和静态非final的属性字段被测试所使用。在整个执行周期内,一个属性必须至少包含最后一次被读取所赋的值。这个比率通过100 * NFE / NF公式进行计算, 其中 NF 表示非final属性的数目,而 NFE 表示的是完全使用过的属性数目。

覆盖输出的类型

JMockit 覆盖工具可以生成如下类型的输出:

  1. HTML 报告: 多页的HTML报告被写入"coverage-report" 路径下, 在当前的工作目录下(如果需要可以顶一个不同的新的输出目录)。 如果目录不存在将会被新建;如果之前已经被生成内容将会被覆盖。报告中包括了测试用例所覆盖的所有java远吗。通常来说工具会查找存在src命名的文件下的所有的以.java为后缀的源码,或者是直接在当前工作目录下; 任何在src和顶级包目录之间的子目录都将会被会搜索。
  2. 覆盖数据文件: 单个命名为"coverage.ser"序列化的文件将会被写入到当前的工作目录下或者是一个特定的输出目录。如果这个文件已经存在,它的内容通过定义既可以重写或者是追加到当前测试运行时的内存结果。这些文件能够被读取或者是处理通过外部的工具。 mockit.coverage.data.CoverageData.readDataFromFile(文件) 方法将会建立一个新的覆盖数据实例包含在指定的序列化文件中的所有的可用覆盖数据。 关于更多的内容,可以通过查看 保存在jmockit-coverage.jar中的API 文档。

调用节点

当使用覆盖工具运行测试套件的时候,一个可选的调用节点信息能够被可以被用户选择性收集。一个调用节点指定是测试指定生产代码行中的测试代码点。

使用这些附加的信息生成覆盖信息将会消耗更多的时间和产生更大的输出; 在另一方面来说, 它将有助于了解在给定生产代码被测试用例运行时那些行被执行。当这些内容被包含在 HTML 报告中, 调用节点列表首次将会被隐藏,但是可以通过简单的店家每一个可执行的代码行进行查看。

配置覆盖工具

为了启用 JMockit 覆盖工具在 JUnit/TestNG 测试运行时, 添加 jmockit.jar 和 jmockit-coverage.jar 到运行时的类路径下。使用 JUnit, 确保 jmockit.jar 出现在路径之前。 (使用JMocikt运行测试用例的细节,可以查看相关的入门教程。)

当没有使用JMockit mocking API的时候, 代码覆盖仍然能够不需要添加jar的方式激活,只需要在运行时候添加 "-javaagent:/jmockit-coverage.jar" 作为 JVM 初始化的参数。

在大多数的例子中,代码覆盖工具不需要额外的配置进行使用。然后有一些工具的工作方式能够被配置。这些可以通过设定一个或者是多个"jmockit-coverage-xyz" 系统属性来进行JVM 实例配置运行测试套件。为了方便可以将 "jmockit-" 前缀忽略,因此"coverage-xyz"也是可用的。

注意你可以很容易在 Ant target, 或者是 Maven surefire plugin 配置, 或是 在IDE中的test run 配置属性, 无论是 JUnit 或是 TestNG; 不需要特定的 JMockit 插件。

可用的配置属性包括:

  1. [jmockit-]coverage-output: 一个或者是多个用逗号分隔的html, html-nocp ("nocp" 达标分调用点), 序列化, 和 序列化添加 , 这些被选定在测试运行的最终结果输出。缺省如果不配置将输出基础的HTML 报告 (html-nocp)。 "html" 和 "html-nocp" 值是互斥的,正如序列化和序列化添加那样。然而可以同时包含每个中的一个。在这种情况下,运行测试之后就会输出两种格式。 使用 "serial" 或是 "serial-append" 将会生成一个命名为"coverage.ser";在 "serial-append"的情况下, 当前测试用例覆盖收集到的数据将会被添加到之前已经存在的数据文件中(如果文件不存在,效果和"serial"一样)。
  2. [jmockit-]coverage-outputDir: 绝对或者是相对的路径针对输出的目录,被用来输出 "coverage.ser" 或者是 "index.html" 文件 (加上 ".html"后缀的html报告文件将自动被生成的子目录下)。 缺省情况下,当前的被使用JVM工作目录将被使用,所有的 以".html"后缀的 html报告文件将被生成到"coverage-report"子目录下。 [jmockit-]coverage-srcDirs: 当生成html报告的时候,逗号分隔的java源文件夹目录将被搜索。(这些通常与序列化数据文件无关)每一个目录将被定义为一个绝对的或者是相对的路径。如果没有定义路径,所有的在当前工作路径下的src目录将被搜索。
  3. [jmockit-]coverage-classes: 无论是类似系统的正则表达式 (使用传统的 "" 和 "?" 通配符), 或者是一个 java.util.regex-conformable 正则表达式。 这给定的表达式将会被用来选择类 (通过全限定名称) 从需要被覆盖测试的产品代码中。缺省情况下,所有的生产代码中的类将在测试的时候被载入,在jar文件中的类不被考虑。 例如"some.package." 选择所有的在some.package 或者是 任何子 包中的类。 作为一个特殊的例子,如果属性被定义为 "loaded", 则所有的类将会被考虑,而不仅仅是只有在JVM运行测试是载入的类; 代码库中的部分不被载入的类将会被忽略。这对于只包含较少的基于代码库的子集合测试的情况,非常有用。
  4. [jmockit-]coverage-excludes: 和之前的属性类似,只是定义代码覆盖测试时候不包含的类名。这个属性可以和 coverage-classes 或者是 它自身相结合使用。 缺省情况下,将没有类被排除。
  5. [jmockit-]coverage-metrics: 一个或是多个逗号分隔的在行(默认), 路径,数据或者是所有的选项,用来表示代码覆盖中所有要收集的度量类型。
  6. [jmockit-]coverage-check: 一个或者是多个分好分隔的规则用来定义在测试之后的最小的覆盖测试检测。缺省的奖没有任何检测将被使用。具体的可以查看检测最小的覆盖章节。

聚集多个测试结果报告

当覆盖工具在一个测试运行之后生成一个报告,它通常会覆盖之前的报告。通常来说,在测试报告中的覆盖数据只会被当前测试所收集的数据影响。现在假定你包含多个测试套件或者是多个运行时的测试配置,你想聚集所有的测试集合的结果到一个html报告中。这就是 "coverage.ser" 序列化数据文件排上用场的时候。

为了激活生成这些文件,我们简单的设定输出系统属性包含 "serial" 或 "serial-append"。正如这两个值所代表的,有多种不同的方式用来组合多分覆盖数据文件。以下的子章节将会详细的介绍每一种情况。

多个数据文件生成聚集报告

假如我们想要聚合多个测试运行结果合并到一个 HTML 报告中。每一个测试需要生成自己的 coverage.ser 文件, 之后它们才可以在最后一个阶段合并成一个报告; 因此每一个测试运行应当被配置为 "coverage-output=serial"。注意,为了保护之前每一个测试生成的 coverage.ser 输出文件,你需要将它们生成或者是复制到不同的路径中。

假设有两个或者是更多的 coverage.ser 文件在分隔的路径下, 一个聚集的报告可以通过执行 mockit.coverage.CodeCoverage.main 方法 (一个普通的 Java "main" 方法)。为了方便操作, jmockit-coverage.jar 文件可执行的。示例,如下的 Ant 任务可能被使用:

<java fork="yes" dir="myBaseDir" jar="jmockit-coverage.jar">
   <jvmarg line="-Djmockit-coverage-output=html"/>
   <arg line="module1-outDir anotherOutDir"/>
</java>

上面一个例子中使用了 "myBaseDir" 作为基地址,当分隔的JVM 实例运行的时候。 两个输出目录包含 "coverage.ser" 数据文件被定义,像使用命令行参数那样。其他的配置参数也可以通过 "coverage-xyz" 系统属性进行配置。这些独立的 JVM 实例将会读取每一个 "coverage.ser" 数据文件,合并这些内存中的覆盖数据,在退出之前然后生成一个聚集的 HTML 报告。

从单个数据文件的每次测试之后生成聚集报告

另外一种方法用来获取聚集多个测试运行的覆盖报告是从所有的测试中获取覆盖的数据进行聚集到一个数据文件中。这能够通过使用所有的测试运行在同一个工作目录货值通过指定覆盖的输出路径为共享路径,当使用 coverage-output=serial-append 针对每一个测试运行的时候。此外,最后的测试应该指定 html 或者是 html-nocp 对于输出 coverage-output 属性 和serial-append。 当然第一个测试用例运行时候不需要从这个文件读取数据;因此在第一个测试运行的时候需要删除文件或者是通过设定第一个测试用例的属性为coverage-output=serial。

总的来说, 关于输出模式 "serial" 和 "serial-append" 的不同是首先在当我们有多个"coverage.ser" 文件的时候(每一个在不容路径的测试使用不同的路径), 然而之后我们将所有的测试共享同一个数据文件。

检测最小的覆盖

如果需要的情况, JMockit Coverage 可以检测最终的覆盖比率在测试运行之后需要满足一个最小值。 这些检测可以通过一个或者是多个检测规则设定在 "coverage-check" 系统属性中 (当包含多个的时候,我们需要使用 ";" 字符进行分割)。

每一个检测规则必须以 "[scope:]最小行比率[,最小路径比率[,最小数据比率]]"格式。 包含三种不同的范围:

  • 总和: 当没有范围设定的时候的默认值。它指定了对于每一个度量的总比率。例如 规则 80 规定了总的行覆盖必须大于 80%, 没有其他的度量的最小值。一个定义了三种度量的例子是 "70,60,85"。注意0可以定义为没有最小值。
  • 每个文件: 定义了每一个文件需要满足的最小比率。如果一个或者是多个文件都以一个很低的比率结束,这个检测就是失败的。例如: "perFile:50,0,40", 意味着每一个源码必须至少 50%以上的行覆盖和至少40%以上的数据覆盖。
  • 包: 定义了在给定包情况下的最小需要满足的总比率,包括子包。例如规则"com.important:90,70"定义了总的文件中的行覆盖在"com.important"包下需要至少90%以上的行覆盖, 而总的路径覆盖需要至少70%。 所有的检测都是在测试运行之后进行的 (实际上是在虚拟机关闭之后)。其他格式的输出(HTML 报告, 序列化文件) 都不会被影响。当一个独立的检测失败了,一个描述信息将会被打印到标准的输出中。如果一个或者是多个检测失败,两个最终的行动将会被执行: 第一个, 一个空的命名为"coverage.check.failed"文件将会被建立在当前工作目录下;第二,一个错误将会被抛出(定义为 AssertionError)。当所有的检测都被执行通过了当前目录下的"coverage.check.failed"会被删除。

通过一个文件来表明覆盖检测是否成功或者是失败,可以允许构建工具做出对应的操作,典型的是当失败的时候建立一个文件。例如我们可以通过如下的Ant 构建脚步来执行:

<fail message="Coverage check failed">
   <condition><available file="coverage.check.failed"/></condition>
</fail>

或者是其他的 Maven pom.xml 文件:

<plugin>
   <artifactId>maven-enforcer-plugin</artifactId>
   <executions>
      <execution>
         <id>coverage.check</id>
         <goals><goal>enforce</goal></goals>
         <phase>test</phase>
         <configuration>
            <rules>
               <requireFilesDontExist>
                  <files><file>coverage.check.failed</file></files>
               </requireFilesDontExist>
            </rules>
         </configuration>
      </execution>
   </executions>
</plugin>

在maven项目中激活覆盖

如果你使用的是 Maven's "test" 目标, 你需要添加以下的依赖在 pom.xml 文件中 (假设 "jmockit.version" 属性被恰当的定义了):

<dependency>
   <groupId>org.jmockit</groupId>
   <artifactId>jmockit</artifactId>
   <version>${jmockit.version}</version>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.jmockit</groupId>
   <artifactId>jmockit-coverage</artifactId>
   <version>${jmockit.version}</version>
   <scope>runtime</scope>
</dependency>

在 Maven 2/3, surefire插件是通常用来实际上运行测试的。为了配置覆盖工具,需要定义适当的"coverage-xyz" 系统属性。例如, 生成文件的输出路径可以通过 coverage-outputDir 属性进行配置。

<plugin>
   <artifactId>maven-surefire-plugin</artifactId>
   <configuration>
      <systemPropertyVariables>
         <coverage-outputDir>target/my-coverage-report</coverage-outputDir>
         <!-- other properties, if needed -->
      </systemPropertyVariables>
   </configuration>
</plugin>

最后,如果测试并没有使用 JMockit mocking APIs,仍然可以使用覆盖工具。在这种情况下, 只需要依赖于"jmockit-coverage"。此外还需要如下配置 surefire 插件:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
      <argLine>
    -javaagent:"${settings.localRepository}"/org/jmockit/jmockit-coverage/${jmockit.version}/
    jmockit-coverage-${jmockit.version}.jar
         <!-- coverage properties, if any are needed -->
      </argLine>
    </configuration>
</plugin>

在maven站点中包含html 报告

为了将JMockit Coverage HTML 报告包含在生成的 Maven site 文档中 , src/site/site.xml 描述文件需要被提供, 使用如下的类似的内容。

<?xml version="1.0" encoding="UTF-8"?>
<project
   xmlns="http://maven.apache.org/DECORATION/1.3.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/DECORATION/1.3.0
                       http://maven.apache.org/xsd/decoration-1.3.0.xsd">
   <body>
      <menu ref="reports"/>
      <menu>
         <item name="Code Coverage Report" href="../../coverage-report/index.html"/>
      </menu>
   </body>
</project>

关闭覆盖输出

有时我们可能需要关闭覆盖输出针对一个特殊的测试运行,而不想通过从classpath 下删除jar。这可以通过两种不同的方法。

其一,我们可以管理相关输出文件的只读属性,当一个已经生成的时候。这个特殊的文件通常在工作目录下, "coverage.ser" 针对序列化的输出 或者是 "coverage-report/index.html" 针对 HTML 输出。文件的属性将在 JMockit 启动时候被使用; 如果一个只读的文件不能被覆盖,JMockit 将彻底避免尝试。

注意工作路径通常是被独立选择的在 Java IDE的测试配置中。同样的 , Java IDE 通常提供一个简单的机制来切换项目中文件的只读属性: 在 IntelliJ IDEA中,通过双击状态栏,当在编辑器中打开一个文件; 在 Eclipse 中,有一个 "Read only" 复选框在 "Properties" 面板中 (可以通过快捷键 "Alt+Enter" 打开) ,当在编辑器中选中一个文本文件的时候。

另外一个切换覆盖的开关是通过简单的设定 coverage-output 系统属性为一个未知的输出格式,例如 "-Dcoverage-output=none"。

独立模式

在前面的章节中我们描述了大部分传统的在使用JUnit/TestNG测试用启用测试覆盖度量的方法。这个工具可以被适用于更加通用的上下文中,尽管: 独立模式可以使得其附加到任何的 Java 6+ 的程序中用来度量行和路径覆盖在指定的类中,无论是什么代码调用了这些类。

为了启用独立模式,目标 JVM 实例必须添加 "-javaagent:jmockit-coverage.jar" 命令行参数。就这样就可以了,不需要 JMockit toolkit jars 出现在目标程序的路径下。覆盖工具的初始化的配置设定可以通过使用之前描述的 "coverage-xyz" 系统属性, 但是它完全是可选的; 配置的属性可以通过一个专用的UI在之后进行修改。

一旦目标进程通过 JMockit Coverage Java 客户端启动, 用户可以通过使用 JMX 客户端进行连接操作其中任意的 "MBeans"。通常来说,标准的 JConsole 工具在 Java 6+ JDK 中将会被使用。 JMockit Coverage MBean 提供了一些配置属性 (和那些可以在命令行中使用 "-D" 的一样), 通过操作可以获取想要生成的输出。JConsole 提供的用户界面在下面显示, 例子中运行覆盖工具的进程是 Tomcat 7 服务实例。

通过JMockit Coverage agent启动的用户界面通过JConsole tool展示, 在连接到tomcate实例之后。

配置属性 (通过一个属性的方式显示在 "CoverageControl" MBean 中) 入之前所述, 除了 "SrcDirs" (代表着覆盖的源码路径)。如果这个属性没有被指定,将会找不到需要进行覆盖的源码文件。

如果 MBean UI 没有被使用, 覆盖输出将会在JVM关闭的时候输出,通过覆盖的属性配置。