zoukankan      html  css  js  c++  java
  • [转载] JaCoCo:分析单元测试覆盖率的利器

    转载自http://www.ibm.com/developerworks/cn/java/j-lo-jacoco/和http://www.cnblogs.com/chenfengmugu/p/4937257.html

    本文主要介绍了如何使用 JaCoCo 分析 Java 项目的单元测试覆盖率,同时介绍了如何在 Eclipse 中安装和使用 EclEMMA,让开发人员可以直观地看到单元测试的覆盖情况。

    前言

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

     

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

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

    安装 EclEmma

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

    图 1. 安装 EclEmma

    图 1. 安装 EclEmma

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

    图 2. Coverage 图标

    图 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. 单元测试覆盖率报告

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

    清单 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. 单元测试覆盖率报告

    图 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 版的单元测试覆盖率报告

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

     

    和同类产品比较

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

    1. JaCoCo 拥有友好的授权形式。JaCoCo 使用了 Eclipse Public License,方便个人用户和商业用户使用。而 Clover 对于商业用户是收费的。
    2. JaCoCo 被良好地集成进各种工具中。在 Java 社区里,很多流行的工具都可以集成 JaCoCo,比如 SonarQube、Jenkins、Netbeans、Eclipse、IntelliJ IDEA、Gradle 等。
    3. JaCoCo 社区非常活跃,它是目前唯一支持 Java 8 的单元测试覆盖率工具。而且关于 JaCoCo 的文档相对较多,降低了学习门槛。
     

    结束语

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

    Jacoco是一个开源的Java代码覆盖率工具,Jacoco可以嵌入到Ant 、Maven中,并提供了EclEmma Eclipse插件,也可以使用JavaAgent技术监控Java程序。很多第三方的工具提供了对Jacoco的集成,如sonar、Jenkins等。
     
    Jacoco远程统计tomcat服务的代码覆盖率
     
    软件安装
    Ant
    Jacoco
     
    Ant 安装步骤
    由于服务器上已经安装了Ant,所以没有亲自安装过在Linux服务器上,百度上有好多文章,可以搜一下,下面就给出一个参考链接
     
    Jacoco安装步骤
    下载下来,随意放在一个目录,解压
    在官网上下载Jacoco,官网地址:http://eclemma.org/jacoco/
    unzip jacoco-0.7.6-20151002.081900-29.zip
     
    Tomcat配置
    1、进入你需要统计代码覆盖率的tomcat  
    cd /home/work/tomcat_8998_launch_feed_1/bin
    2、先关闭这个tomcat的服务
    正常情况下可以直接执行  ./shutdown.sh
    执行结束后 ps -ef|grep 端口号 查看是否已经正常关闭,如果还没有关闭 kill -9 pid(来源于ps的结果)
    3、修改 catalina.sh  的JAVA_OPTS配置
    JAVA_OPTS="$JAVA_OPTS -javaagent:/home/work/zj/jacoco/lib/jacocoagent.jar=includes=*,output=file,append=true,destfile=/home/work/zj/jacoco/target/jacoco.exec"
    具体参数的意思请查看官方文档
    4、启动服务 ./startup.sh
    5、查看一下JAVA_OPTS是否有修改正确,我掉在这个坑里了,有一种情况是你把JAVA_OPTS写在前面,在后面被别人的覆盖了
    ps -ef|grep 端口号 看出来的结果有没有刚才你修改的值
     
     
    Ant配置
    随意进入一个目录新建build.xml,build.xml见附件
     View Code
    需要注意的点:
    1、需要上传源代码到服务器 
    2、如果你的web工程里面还引入了别的工程,例如你现在tomcat所部署的war包只是web的入口,实际的逻辑,模型等都作为jar包放在这个工程的lib里面的时候,
    此时你又要统计这些逻辑模型的代码覆盖率,那么你需要做的是:1、把lib中你需要统计覆盖率的jar包解压出来;2、把jar包对应的源代码上传上去;3、build.xml中
    structure下面建立多个group,对应的源文件和类文件也要新增进去;
     
     
    覆盖率报告
    1、由于我使用的是file的方式,因此需要获得覆盖率的话,需要shutdown tomcat,执行./shutdown.sh ,如果不行的话,执行kill但是千万不能执行kill -9
    执行完kill后,会把覆盖率dump到jacoco.exec
    2、在build.xml所在的目录执行ant report将会把报告输出到相应的目录下面
    3、在报告所在的目录直接打开index.html将会看到所有的覆盖率,如果想在线查看report的话,可以在nginx或者tomcat下搭建
     
  • 相关阅读:
    html实现 省——市——区三级联动
    test
    JAVA课程设计——坦克大战
    Java MOOC-互评作业-流与文件
    DS博客作业06--图
    DS博客作业08--课程总结
    DS博客作业07--查找
    DS博客作业06--图
    DS博客作业05-树
    DS博客作业01--日期抽象数据类型设计与实现
  • 原文地址:https://www.cnblogs.com/scott19820130/p/5950482.html
Copyright © 2011-2022 走看看