如何使用JaCoCo分析java单元测试覆盖率

前言

随着敏捷开发的流行,编写单元测试已经成为业界共识。但如何来衡量单元测试的质量呢?有些管理者片面追求单元测试的数量,导致底下的开发人员投机取巧,编写出大量的重复测试,数量上去了,质量却依然原地踏步。相比单纯追求单元测试的数量,分析单元测试的代码覆盖率是一种更为可行的方式。

JaCoCo(Java Code Coverage)就是一种分析单元测试覆盖率的工具,使用它运行单元测试后,可以给出代码中哪些部分被单元测试测到,哪些部分没有没测到,并且给出整个项目的单元测试覆盖情况百分比,看上去一目了然。

EclEmma 是基于 JaCoCo 的一个 Eclipse 插件,开发人员可以方便的和其交互。因此,本文先从 EclEmma 入手,给读者一个直观的体验。

使用 EclEmma 在 Eclipse 中查看单元测试覆盖率

EclEmma 是基于 JaCoCo 的 Eclipse 插件,使用它,开发人员可以直观地看到单元测试的覆盖情况。

安装 EclEmma

打开 Eclipse 的软件市场,在其中搜索 EclEmma,找到后完成安装,如下图所示:

图 1. 安装 EclEmma

安装完成后,Eclipse 的工具条里会多出下面这样一个图标:

图 2. Coverage 图标

分析单元测试覆盖率

成功安装 EclEmma 后,就可以试着用它来分析项目的单元测试覆盖率了。为了方便演示,我们使用 Eclipse 创建了一个标准 Java 工程。其中包含一个数学工具类,用来计算三个数中的最大值,代码如下:

清单 1. 数学工具类

package com.dw.math;
public class MathUtil {
public static int max(int a, int b, int c){
if(a > b){
if(a > c){
return a;
}else{
return c;
}
}else{
if(b > c){
return b;
}else{
return c;
}
}
}
}

可以看到,这里的算法稍微有点复杂,使用到了多个条件判断分支,因此,特别适合为其编写单元测试。第一个版本的单元测试如下:

清单 2. 第一个版本的单元测试

package com.dw.math;
import static org.junit.Assert.*;
import org.junit.Test;
public class MathUtilTest {
@Test
public void test_max_1_2_3() {
assertEquals(3, MathUtil.max(1, 2, 3));
}
}

试着运行一下单元测试覆盖率分析工具:40.0%!似乎不太理想。展开分析报告,双击后在编辑器里可以看到覆盖情况被不同的颜色标识出来,其中绿颜色表示代码被单元测试覆盖到,黄色表示部分覆盖,红色则表示完全没有覆盖到,如下图所示:

图 3. 单元测试覆盖率报告

让我们尝试多加一些单元测试,来改善这种情况,请看下面第二个版本的单元测试:

清单 3. 第二个版本的单元测试

package com.dw.math;
import static org.junit.Assert.*;
import org.junit.Test;
public class MathUtilTest {
@Test
public void test_max_1_2_3() {
assertEquals(3, MathUtil.max(1, 2, 3));
}
@Test
public void test_max_10_20_30() {
assertEquals(30, MathUtil.max(10, 20, 30));
}
@Test
public void test_max_100_200_300() {
assertEquals(300, MathUtil.max(100, 200, 300));
}
}

测试覆盖率还是 40.0%!虽然我们额外再加了两个测试,但覆盖率没有半点提升,这些单元测试其实是重复的,它们在重复测试同一段代码。如果单纯追求单元测试的数量,那么这无疑会给管理者造成错觉,他们觉得单元测试的数量增加了,软件的质量更有保证了;而对于那些喜欢偷懒的程序员,也蒙混过关,但却给软件质量埋下了隐患。让我们删掉这些重复的单元测试,重新思考一下怎么测试这个方法。

首先我们要测试正常情况,这其中又包含 3 种情况:第一个参数最大,第二个参数最大,以及最后一个参数最大。然后我们还需测试几种特殊情况,比如三个参数相同,三个参数中,其中两个相同。让我们照此思路重新编写单元测试:

清单 4. 第三个版本的单元测试

package com.dw.math;
import static org.junit.Assert.*;
import org.junit.Test;
public class MathUtilTest {
@Test
public void test_max_1_2_3() {
assertEquals(3, MathUtil.max(1, 2, 3));
}
@Test
public void test_max_1_3_2() {
assertEquals(3, MathUtil.max(1, 3, 2));
}
@Test
public void test_max_3_2_1() {
assertEquals(3, MathUtil.max(3, 2, 1));
}
@Test
public void test_max_0_0_0(){
assertEquals(0, MathUtil.max(0, 0, 0));
}
@Test
public void test_max_0_1_0(){
assertEquals(1, MathUtil.max(0, 1, 0));
}
}

再次运行单元测试分析工具:75.0%!这次比以前有了很大提升,但是结果还不能令人满意,打开分析报告可以看到,有一个分支还是没有覆盖到,如图所示:

图 4. 单元测试覆盖率报告

阅读代码可以看出,这种情况是指第一个参数大于第二个参数,却小于第三个参数,因此我们再增加一个单元测试:

清单 5. 再增加一个单元测试

@Test
public void test_max_2_1_3() {
assertEquals(3, MathUtil.max(2, 1, 3));
}

再运行一遍单元测试分析工具:100.0%!终于我们的单元测试达到了全覆盖,这样我们对自己开发的代码更有信心了。当然,我们在这里并不是为了单纯的追求这个数字,在增加单元测试覆盖率的诱导下,我们重新理清了测试的步骤,写出了更有意义、更全面的单元测试。而且根据单元测试分析工具给的反馈,我们还发现了先前没有想到的情形。因此,单元测试的覆盖率并不只是一个为了取悦管理者的数据,它实实在在地帮助我们改善了代码的质量,增加了我们对所编写代码的信心。

给管理者的单元测试覆盖率报告

管理者天生喜欢阅读报告。他们不会屈尊坐在你的办公桌前,让你向他展示 Eclipse 中这一片花花绿绿的东西。而且这份报告对他们至关重要,他们需要用它向上级汇报;年底回顾时,他们也可以兴奋地宣称产品的单元测试覆盖率增加了多少。作为一名开发人员,我们很大一部分工作量就在于满足管理者的这种需求。因此,本节我们讨论如何将 JaCoCo 集成到 Ant 脚本中,生成漂亮的单元测试覆盖率报告。

准备工作

在集成 JaCoCo 前,必须先确保你的 Java 工程有一个可执行的 Ant 构建脚本。一个简单的 Ant 构建脚本一般会执行如下任务:编译(包括编译工程代码和测试代码)、打包和执行单元测试。下面是本文示例 Java 项目所用的 Ant 构建脚本,读者可结合自己的项目及文件路径,在此基础之上进行修改。

清单 6. build.xml

<project name="math" basedir="." default="junit">
<!--预定义的属性和 classpath -->
<property name="src.dir" value="src" />
<property name="test.dir" value="test" />
<property name="build.dir" value="build" />
<property name="classes.dir" value="${build.dir}/classes" />
<property name="tests.dir" value="${build.dir}/tests" />
<property name="jar.dir" value="${build.dir}/jar" />
<property name="lib.dir" value="lib" />
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar" />
</path>
<!--清除上次构建 -->
<target name="clean">
<delete dir="${build.dir}" />
</target>
<!--编译代码,包括单元测试 -->
<target name="compile" depends="clean">
<mkdir dir="${classes.dir}" />
<mkdir dir="${tests.dir}" />
<javac srcdir="${src.dir}" destdir="${classes.dir}" />
<javac srcdir="${test.dir}" destdir="${tests.dir}">
<classpath>
<path refid="classpath" />
<path location="${classes.dir}" />
</classpath>
</javac>
</target>
<!--打包 -->
<target name="jar" depends="compile">
<mkdir dir="${jar.dir}" />
<jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
</jar>
</target>
<!--运行单元测试 -->
<target name="junit" depends="jar">
<junit printsummary="yes">
<classpath>
<path refid="classpath"/>
<path location="${classes.dir}" />
<path location="${tests.dir}" />
</classpath>
<batchtest fork="yes">
<fileset dir="${test.dir}" includes="**/*Test.java"/>
</batchtest>
</junit>
</target>
</project>

集成 JaCoCo

首先需要从 然后就是使用 JaCoCo 官网下载 需要的版本,然后将下载得到的压缩文件解压,将其中的 jacocoant.jar 拷贝至 Java 工程下存放第三方 jar 包的目录,在示例工程里,我有一个和 src 平级的 lib 目录,jacocoant.jar 就放到了这个目录底下,读者可根据自己的项目组织结构做相应调整。然后我们需要在 Ant 构建脚本中定义新的任务:

清单 7. 定义新的构建任务

<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath refid="classpath" />
</taskdef>

现在就可以在 Ant 构建脚本中使用 JaCoCo 了。需要注意的是,为了避免命名冲突,需要给 Ant 构建脚本加入新的 XML 命名空间:

清单 8. 加入新的 JaCoCo 命名空间

<project name="math" basedir="." xmlns:jacoco="antlib:org.jacoco.ant" default="junit">

我们主要使用 JaCoCo 的两个任务:首先是jacoco:coverage,用来生成单元测试覆盖率数据,这是一个二进制文件,为了生成从该文件生成报表,我们还要调用另外一个任务jacoco:report,它的输入为jacoco:coverage生成的二进制文件,输出报表。报表有多种格式可选,可以是 HTML、XML、CSV 等。具体的脚本如下:

清单 9. 使用 JaCoCo 生成测试覆盖率和报表

<jacoco:coverage destfile="${build.dir}/jacoco.exec">
<junit fork="true" forkmode="once" printsummary="yes">
<classpath>
<path refid="classpath" />
<path location="${classes.dir}" />
<path location="${tests.dir}" />
</classpath>
<batchtest fork="yes">
<fileset dir="${test.dir}" includes="**/*Test.java"/>
</batchtest>
</junit>
</jacoco:coverage>
<jacoco:report>
<executiondata>
<file file="${build.dir}/jacoco.exec"/>
</executiondata>
<structure name="dw demo">
<classfiles>
<fileset dir="${classes.dir}"/>
</classfiles>
<sourcefiles encoding="UTF-8">
<fileset dir="${src.dir}"/>
</sourcefiles>
</structure>
<html destdir="${build.dir}"/>
</jacoco:report>

JaCoCo 的任务定义非常清晰,在这里略作说明。首先需要将原来的junit任务嵌入jacoco:coverage,而且需要指定fork="true",代表单元测试需要另起一个 JVM 执行,否则 JaCoCo 就会执行失败。destfile="${build.dir}/jacoco.exec"指定生成的测试覆盖率文件输出到什么地方,后面生成报告的时候需要输入该文件的地址。

然后就是使用 jacoco:report 生成报告,指定前面任务生成的单元测试覆盖率文件、编译好的类文件以及源代码,最后选择一种格式,这里使用 html,生成报告。打开报告的存放路径,就可以看到如下所示的单元测试覆盖率报告:

图 5. HTML 版的单元测试覆盖率报告

和同类产品比较

市面上流行的单元测试覆盖率工具还有 Clover 和 Cobertura。和它们相比,JaCoCo 有如下优势:

JaCoCo 拥有友好的授权形式。JaCoCo 使用了 Eclipse Public License,方便个人用户和商业用户使用。而 Clover 对于商业用户是收费的。

JaCoCo 被良好地集成进各种工具中。在 Java 社区里,很多流行的工具都可以集成 JaCoCo,比如 SonarQube、Jenkins、Netbeans、Eclipse、IntelliJ IDEA、Gradle 等。

JaCoCo 社区非常活跃,它是目前唯一支持 Java 8 的单元测试覆盖率工具。而且关于 JaCoCo 的文档相对较多,降低了学习门槛。

结束语

本文为大家介绍了如何使用 JaCoCo 分析项目的单元测试覆盖率,文章先从 JaCoCo 的 Eclipse 插件 EclEmma 开始,直观地介绍了如何一步步提高单元测试质量,最终达到对代码的全覆盖;然后为大家介绍了如何将 JaCoCo 集成到 Ant 构建脚本中,生成漂亮的单元测试覆盖率报告。但是使用 JaCoCo 只是第一步,重要的是开发人员能够根据工具所给的反馈,不断改进自己的单元测试,写出高质量的代码。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 深入学习Java单元测试(Junit+Mock+代码覆盖率)

    前言 单元测试是编写测试代码,用来检测特定的.明确的.细颗粒的功能.单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的. 单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复.改进或重构之后的正确性. 一般来说,单元测试任务包括 1.接口功能测试:用来保证接口功能的正确性. 2.局部数据结构测试(不常用):用来保证接口中的数据结构是正确的 1.比如变量有无初始值 2.变量是否溢出 3.边界条件测试 1.变量没有赋值(即为NULL) 2.变量是数值(或字符) 1.主要边界

  • 如何使用JaCoCo分析java单元测试覆盖率

    前言 随着敏捷开发的流行,编写单元测试已经成为业界共识.但如何来衡量单元测试的质量呢?有些管理者片面追求单元测试的数量,导致底下的开发人员投机取巧,编写出大量的重复测试,数量上去了,质量却依然原地踏步.相比单纯追求单元测试的数量,分析单元测试的代码覆盖率是一种更为可行的方式. JaCoCo(Java Code Coverage)就是一种分析单元测试覆盖率的工具,使用它运行单元测试后,可以给出代码中哪些部分被单元测试测到,哪些部分没有没测到,并且给出整个项目的单元测试覆盖情况百分比,看上去一目了然

  • 在idea中使用JaCoCo插件统计单元测试覆盖率的实现

    在后台工程师开发完新代码交给QA进行测试时,软件测试人员一般都会要求后台开发对单元测试的覆盖率达到一定的标准:例如我们的标准是分支覆盖率达到50%以上,函数覆盖率达到50%以上. 在idea中查看覆盖率挺简单的,idea中支持三种插件来查看覆盖率,每种插件统计明细各有千秋,分别是idea自带插件.JaCoCo.Emma. 在idea中打开一个测试类,点击右上角"Edit Configurations"按钮,如下图所示: 然后会弹出一个框,左侧菜单栏显示JUnit栏目,右边我们选择&qu

  • 使用Jacoco获取 Java 程序的代码执行覆盖率的步骤详解

    Jacoco是Java Code Coverage的缩写,顾名思义,它是获取Java代码执行覆盖率的一个工具,通常用它来获取单元测试覆盖率.它通过分析Java字节码来得到代码执行覆盖率,因此它还可以分析任何基于JVM的语言(如Croovy.Kotlin)的覆盖率.本文不讨论如何用Jacoco获取单元测试的代码覆盖率,而是从Jacoco的原理出发,介绍如何通过Jacoco获取SIT或者UAT的测试覆盖率.更准确来讲,是获取一个应用执行过的代码占总代码的比率.包括字节码指令覆盖率,分支覆盖率,圈复杂

  • 使用XDebug调试及单元测试覆盖率分析

    今天我就就自己对XDebug使用的一些体验做一小段分享.XDebug也是因为需要是用来生成覆盖率分析文件才安装的,刚接触不久,平时用的也不是很频繁,但是这个的确是一个好工具,如果想要依赖它来分析程序的性能还是需要自己亲手去试试.具体它有多好,请听我一一道来. <!--[if !supportLists]-->一.<!--[endif]-->安装篇(XDebug 和PHPUnit) A:安装XDebug: Xdebug网下载xdebug  dll文件,存放到php加载的ext目录下(

  • Java单元测试Powermockito和Mockito使用总结

    目录 依赖引入 PowerMockito的使用 使用mockito来mock实例 mock对Redis的静态调用 mock单例类 mock私有方法 PowerMock跳过方法执行 总结 参考文档 最近公司在推进Java应用的单元测试,要求将单元测试的覆盖率提高到50%以上,保证上线代码充分自测.公司单元测试框架选用了Junit 4.12,Mock框架选用了Mockito和PowerMock,同时选用JaCoCo来做覆盖率检测,下面详细介绍一下我在使用这几个框架的一些经验. 依赖引入 <depen

  • 详解java JDK 动态代理类分析(java.lang.reflect.Proxy)

    详解java JDK 动态代理类分析(java.lang.reflect.Proxy) /** * JDK 动态代理类分析(java.lang.reflect.Proxy使用) * * @author 张明学 * */ public class ProxyStudy { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { // 动态代理类:通用指定类加载器,和接

  • 如何利用JConsole观察分析Java程序的运行并进行排错调优

    一.JConsole是什么 从Java 5开始 引入了 JConsole.JConsole 是一个内置 Java 性能分析器,可以从命令行或在 GUI shell 中运行.您可以轻松地使用 JConsole(或者,它更高端的 "近亲" VisualVM )来监控 Java 应用程序性能和跟踪 Java 中的代码. 二.如何启动JConsole 1.如果是从命令行启动,使 JDK 在 PATH 上,运行 jconsole 即可. 2.如果从 GUI shell 启动,找到 JDK 安装路

  • 分析java 中AspectJ切面执行两次的原因

    分析java 中AspectJ切面执行两次的原因 背景 转眼之间,发现博客已经将近半年没更新了,甚是惭愧.话不多说,正如标题所言,最近在使用AspectJ的时候,发现拦截器(AOP切面)执行了两次了.我们知道,AspectJ是AOP的一种解决方案,本质上是通过代理类在目标方法执行通知(Advice),然后由代理类再去调用目标方法.所以,从这点讲,拦截器应该只会执行一次.但是在测试的时候发现拦截器执行了两次. 问题重现 既然问题已经明了,那么可以通过代码简单重现这个问题,从而更深层次分析到底是什么

  • java单元测试JUnit框架原理与用法实例教程

    本文实例讲述了java单元测试JUnit框架原理与用法.分享给大家供大家参考,具体如下: 1 简介 JUnit是一个Java语言的单元测试框架,它由 Kent Beck 和 Erich Gamma 建立,逐渐成为 xUnit 家族中最为成功的一个. JUnit有它自己的JUnit扩展生态圈,多数Java的开发环境都已经集成了JUnit作为单元测试的工具.在这里,一个单元可以是一个方法.类.包或者子系统.因此,单元测试是指对代码中的最小可测试单元进行检查和验证,以便确保它们正常工作.例如,我们可以

随机推荐