`
nanjingjiangbiao_T
  • 浏览: 2594085 次
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

使用Cobertura统计单元测试覆盖率

 
阅读更多
转载自: http://terrencexu.iteye.com/blog/718834
--- James Gosling mused: "I don't think anybody tests enough of anything."

做单元测试是developer都要接触的事情,工具也基本上都是选择JUnit或者TestNG,但是无论是JUnit还是TestNG都只能得出一个测试用例相关的报表。

从这个报表中我们能得信息是,测试用例的执行情况,成功率,失败率,哪个失败了等等。通过这份报表我们并不能得悉我们是否把所有的功能代码都测试到了,那么这时候我们就需要引入单元测试覆盖率的概念了。

单元测试覆盖率通俗的讲就是多少行代码被测试用例运行到了,多少个block被执行了,多少个包被执行了等,通过这些数据我们可以清楚的了解测试的覆盖率情况,进而反向的改善已有的或者新添加测试用例去尽可能多的覆盖功能代码,block等,以提高代码的可信赖度。

对于Java而言,进行覆盖率分析的方式有三类:第一种是将instrumentation(不知道怎么翻译好,测试仪表?),直接加入到源代码中;第二种是将instrumentation加入到编译好的Java字节码中;第三种是在一个可编辑的虚拟机中运行代码。Cobertura选择了第二种方式。

为了便于使用,Cobertura提供了两种方式将Cobertura集成到已有的运行环境中: Ant和命令行

总结起来Cobertura做的事情就是:

1. Cobertura将instrumentation加入到编译好的需要被检测的Java类的字节码中。
2. 运行测试用例的时候Cobertura通过之前安插好的instrumentation统计每一行代码是否被执行,所有被植入instrumentation的类的序列化信息将被写入cobertura.ser。
3. 根据统计结果生成报表,格式为XML或者HTML。

整个过程不需要我们额外写任何Java代码,只需要通过ant脚本或者命令行触发相应的操作。

下面首先介绍一下使用ant脚本的方式。

第一步,下载最新的Cobertura,解压。下载地址为http://cobertura.sourceforge.net/download.html。
第二步,将Cobertura目录下面的Cobertura.jar和lib下所有jar拷贝到你的工程的某个目录下。
第三步,创建ant脚本,或者在已有的ant脚本中添加相应的target。

现在开始设置ant脚本
第一步,将cobertura.jar以及Cobertura/lib下的所有jar引入classpath
Xml代码 复制代码收藏代码
  1. <pathid="lib.classpath">
  2. <filesetdir="${lib.dir}">
  3. <includename="**/*.jar"/>
  4. </fileset>
  5. </path>

注:lib.dir是你存放cobertura.jar以及/Conbertura/lib/*.jar的地方

第二步,将cobertura自身定义的task引入到ant脚本中
Xml代码 复制代码收藏代码
  1. 1.<taskdefclasspathref="lib.classpath"resource="tasks.properties"/>

第三步,编译工程代码到某个目录,比如${src.java.classes.dir}

注:你可以选择将所有的业务代码和测试代码编译到一个classes目录下,或者选择编译到不同的目录下,在本例中将使用不同的目录存放java.src和test.src。
Xml代码 复制代码收藏代码
  1. <targetname="compile"depends="init">
  2. <javacsrcdir="${src.java.dir}"destdir="${src.java.classes.dir}"debug="yes">
  3. <classpathrefid="lib.classpath"/>
  4. </javac>
  5. <javacsrcdir="${src.test.dir}"destdir="${src.test.classes.dir}"debug="yes">
  6. <!--Thisisveryimporttoincludethesrc.java.classes.dirhere-->
  7. <classpathlocation="${src.java.classes.dir}"/>
  8. <classpathrefid="lib.classpath"/>
  9. </javac>
  10. </target>

注:src.java.dir存放所有的将被测试的java类,src.java.classes.dir存放java类的编译字节码;src.test.dir存放所有的测试用例, src.test.classes.dir存放测试用例的编译字节码。init target用来创建一些备用的目录,将包含在附件的完整工程代码中。

第四步,定义target,向生成的java.class里插入instrumentation,test.class将不插入instrumentation,因为我们不关心测试用例本身的覆盖率。
Xml代码 复制代码收藏代码
  1. <targetname="instrument">
  2. <!--Removethecoveragedatafileandanyoldinstrumentationclasses-->
  3. <deletefile="cobertura.ser"/>
  4. <deletedir="${instrumented.classes.dir}"/>
  5. <!--Instrumenttheapplicationclasses,writingtheinstrumentedclassesinto${instrumented.classes.dir}-->
  6. <cobertura-instrumenttodir="${instrumented.classes.dir}">
  7. <!--Thefollowinglinecausesinstrumenttoignoreanysourcelinecontainingareferencetolog4j,forthepurposeofcoveragereporting-->
  8. <ignoreregex="org.apache.log4j.*"/>
  9. <filesetdir="${src.java.classes.dir}">
  10. <includename="**/*.class"/>
  11. </fileset>
  12. </cobertura-instrument>
  13. </target>

注:instrumented.classes.dir存在所有被植入instrumentation的Java class。如果java代码和测试用例被编译到了同一个目录下,可以使用如<exclude name="**/*Test.class" />忽略测试用例。

第五步,执行测试用例,同时Cobertura将在后台统计代码的执行情况。这一步就是普通的junit的target,将执行所有的测试用例,并生成测试用例报表。
Xml代码 复制代码收藏代码
  1. <targetname="test"depends="init,compile">
  2. <junitfork="yes"dir="${basedir}"failureProperty="test.failed">
  3. <!--Note:theclasspathorder:instrumentedclassesarebeforetheoriginal(uninstrumented)classes.Thisisimportant!!!-->
  4. <classpathlocation="${instrumented.classes.dir}"/>
  5. <classpathlocation="${src.java.classes.dir}"/>
  6. <classpathlocation="${src.test.classes.dir}"/>
  7. <classpathrefid="lib.classpath"/>
  8. <formattertype="xml"/>
  9. <testname="${testcase}"todir="${reports.junit.xml.dir}"if="testcase"/>
  10. <batchtesttodir="${reports.junit.xml.dir}"unless="testcase">
  11. <filesetdir="${src.test.dir}">
  12. <includename="**/*.java"/>
  13. </fileset>
  14. </batchtest>
  15. </junit>
  16. <junitreporttodir="${reports.junit.xml.dir}">
  17. <filesetdir="${reports.junit.xml.dir}">
  18. <includename="TEST-*.xml"/>
  19. </fileset>
  20. <reportformat="frames"todir="${reports.junit.html.dir}"/>
  21. </junitreport>
  22. </target>


注:这一步非常需要注意的是${instrumented.classes.dir}应该最先被引入classpath.
It is important to set fork="true" because of the way Cobertura works. It only flushes its changes to the coverage data file to disk when the JVM exits. If JUnit runs in the same JVM as ant, then the coverage data file will be updated AFTER ant exits, but you want to run cobertura-report BEFORE ant exits.

For this same reason, if you're using ant 1.6.2 or higher then you might want to set forkmode="once". This will cause only one JVM to be started for all your JUnit tests, and will reduce the overhead of Cobertura reading/writing the coverage data file each time a JVM starts/stops.

第六步,生成测试覆盖率报表。
Xml代码 复制代码收藏代码
  1. <targetname="alternate-coverage-report">
  2. <!--GenerateaseriesofHTMLfilescontainingthecoveragedatainauser-readableformusingnestedsourcefilesets-->
  3. <cobertura-reportdestdir="${coverage.cobertura.html.dir}">
  4. <filesetdir="${src.java.dir}">
  5. <includename="**/*.java"/>
  6. </fileset>
  7. </cobertura-report>
  8. </target>

注:因为我将Java代码和测试用例分别放在不同的包中,所以如果你的代码都放在一个包中的话,应该使用<exclude name="**/*Test.java" />剔除测试用例; coverage.cobertura.html.dir是存放report的地方。生成XML报表的方式将在完成的 build.xml文件中给出。

到此我们已经完成了生成测试覆盖率报表的全部工作,如果还想验证一下测试覆盖率,可以通过以下方式
Xml代码 复制代码收藏代码
  1. <targetname="coverage-check">
  2. <cobertura-checkbranchrate="34"totallinerate="100"/>
  3. </target>

If you do not specify branchrate, linerate, totalbranchrate or totallinerate, then Cobertura will use 50% for all of these values.
引用自:
http://cobertura.sourceforge.net/anttaskreference.html
Java代码 复制代码收藏代码
  1. <cobertura-checkbranchrate="30"totalbranchrate="60"totallinerate="80">
  2. <regexpattern="com.example.reallyimportant.*"branchrate="80"linerate="90"/>
  3. <regexpattern="com.example.boringcode.*"branchrate="40"linerate="30"/>
  4. </cobertura-check>

cobertura-merge Task
You must tell Cobertura which coverage data files to merge by passing in standard ant filesets.

Xml代码 复制代码收藏代码
  1. <cobertura-merge>
  2. <filesetdir="${test.execution.dir}">
  3. <includename="server/cobertura.ser"/>
  4. <includename="client/cobertura.ser"/>
  5. </fileset>
  6. </cobertura-merge>

现在给出完成的build.xml文件,仅供参考:
Xml代码 复制代码收藏代码
  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <projectname="study-cobertura"default="coverage"basedir=".">
  3. <description>Theantfileforstudy-cobertuna</description>
  4. <propertyname="src.java.dir"value="${basedir}/java"/>
  5. <propertyname="src.test.dir"value="${basedir}/test"/>
  6. <propertyname="build.dir"value="${basedir}/build"/>
  7. <propertyname="src.java.classes.dir"value="${build.dir}/src-java-classes"/>
  8. <propertyname="src.test.classes.dir"value="${build.dir}/src-test-classes"/>
  9. <propertyname="instrumented.classes.dir"value="${build.dir}/instrumented-classes"/>
  10. <propertyname="reports.dir"value="${basedir}/reports"/>
  11. <propertyname="reports.junit.xml.dir"value="${reports.dir}/junit-xml"/>
  12. <propertyname="reports.junit.html.dir"value="${reports.dir}/junit-html"/>
  13. <propertyname="coverage.cobertura.xml.dir"location="${reports.dir}/cobertura-xml"/>
  14. <propertyname="coverage.cobertura.summary.dir"location="${reports.dir}/cobertura-summary-xml"/>
  15. <propertyname="coverage.cobertura.html.dir"location="${reports.dir}/cobertura-html"/>
  16. <propertyname="lib.dir"location="${basedir}/lib"/>
  17. <pathid="lib.classpath">
  18. <filesetdir="${lib.dir}">
  19. <includename="**/*.jar"/>
  20. </fileset>
  21. </path>
  22. <taskdefclasspathref="lib.classpath"resource="tasks.properties"/>
  23. <targetname="init">
  24. <mkdirdir="${src.java.classes.dir}"/>
  25. <mkdirdir="${src.test.classes.dir}"/>
  26. <mkdirdir="${instrumented.classes.dir}"/>
  27. <mkdirdir="${reports.dir}"/>
  28. <mkdirdir="${reports.junit.html.dir}"/>
  29. <mkdirdir="${reports.junit.xml.dir}"/>
  30. <mkdirdir="${coverage.cobertura.xml.dir}"/>
  31. <mkdirdir="${coverage.cobertura.summary.dir}"/>
  32. <mkdirdir="${coverage.cobertura.html.dir}"/>
  33. </target>
  34. <targetname="compile"depends="init">
  35. <javacsrcdir="${src.java.dir}"destdir="${src.java.classes.dir}"debug="yes">
  36. <classpathrefid="lib.classpath"/>
  37. </javac>
  38. <javacsrcdir="${src.test.dir}"destdir="${src.test.classes.dir}"debug="yes">
  39. <!--Thisisveryimporttoincludethesrc.java.classes.dirhere-->
  40. <classpathlocation="${src.java.classes.dir}"/>
  41. <classpathrefid="lib.classpath"/>
  42. </javac>
  43. </target>
  44. <targetname="instrument">
  45. <!--Removethecoveragedatafileandanyoldinstrumentationclasses-->
  46. <deletefile="cobertura.ser"/>
  47. <deletedir="${instrumented.classes.dir}"/>
  48. <!--Instrumenttheapplicationclasses,writingtheinstrumentedclassesinto${instrumented.classes.dir}-->
  49. <cobertura-instrumenttodir="${instrumented.classes.dir}">
  50. <!--Thefollowinglinecausesinstrumenttoignoreanysourcelinecontainingareferencetolog4j,forthepurposeofcoveragereporting-->
  51. <ignoreregex="org.apache.log4j.*"/>
  52. <filesetdir="${src.java.classes.dir}">
  53. <includename="**/*.class"/>
  54. </fileset>
  55. </cobertura-instrument>
  56. </target>
  57. <targetname="test"depends="init,compile">
  58. <junitfork="yes"dir="${basedir}"failureProperty="test.failed">
  59. <!--Note:theclasspathorder:instrumentedclassesarebeforetheoriginal(uninstrumented)classes.Thisisimportant!!!-->
  60. <classpathlocation="${instrumented.classes.dir}"/>
  61. <classpathlocation="${src.java.classes.dir}"/>
  62. <classpathlocation="${src.test.classes.dir}"/>
  63. <classpathrefid="lib.classpath"/>
  64. <formattertype="xml"/>
  65. <testname="${testcase}"todir="${reports.junit.xml.dir}"if="testcase"/>
  66. <batchtesttodir="${reports.junit.xml.dir}"unless="testcase">
  67. <filesetdir="${src.test.dir}">
  68. <includename="**/*.java"/>
  69. </fileset>
  70. </batchtest>
  71. </junit>
  72. <junitreporttodir="${reports.junit.xml.dir}">
  73. <filesetdir="${reports.junit.xml.dir}">
  74. <includename="TEST-*.xml"/>
  75. </fileset>
  76. <reportformat="frames"todir="${reports.junit.html.dir}"/>
  77. </junitreport>
  78. </target>
  79. <targetname="coverage-check">
  80. <cobertura-checkbranchrate="34"totallinerate="100"/>
  81. </target>
  82. <!--=================================
  83. target:coverage-report
  84. =================================-->
  85. <targetname="coverage-report">
  86. <!--GenerateanXMLfilecontainingthecoveragedatausingthe'srcdir'attribute-->
  87. <cobertura-reportsrcdir="${src.java.dir}"destdir="${coverage.cobertura.xml.dir}"format="xml"/>
  88. </target>
  89. <!--=================================
  90. target:summary-coverage-report
  91. =================================-->
  92. <targetname="summary-coverage-report">
  93. <!--GenerateansummaryXMLfilecontainingthecoveragedatausingthe'srcidir'attribute-->
  94. <cobertura-reportsrcdir="${src.java.dir}"destdir="${coverage.cobertura.summary.dir}"format="summaryXml"/>
  95. </target>
  96. <!--=================================
  97. target:alternate-coverage-report
  98. =================================-->
  99. <targetname="alternate-coverage-report">
  100. <!--GenerateaseriesofHTMLfilescontainingthecoveragedatainauser-readableformusingnestedsourcefilesets-->
  101. <cobertura-reportdestdir="${coverage.cobertura.html.dir}">
  102. <filesetdir="${src.java.dir}">
  103. <includename="**/*.java"/>
  104. </fileset>
  105. </cobertura-report>
  106. </target>
  107. <!--=================================
  108. target:clean
  109. =================================-->
  110. <targetname="clean"description="Removeallfilescreatedbythebuild/testprocess">
  111. <deletedir="${src.java.classes.dir}"/>
  112. <deletedir="${src.test.classes.dir}"/>
  113. <deletedir="${instrumented.classes.dir}"/>
  114. <deletedir="${reports.dir}"/>
  115. <deletefile="cobertura.log"/>
  116. <deletefile="cobertura.ser"/>
  117. </target>
  118. <!--=================================
  119. target:coverage
  120. =================================-->
  121. <targetname="coverage"depends="clean,compile,instrument,test,coverage-report,summary-coverage-report,alternate-coverage-report"description="Compile,instrumentourself,runthetestsandgenerateJUnitandcoveragereports."/>
  122. </project>


一定要在ant清除脚本中加入删除cobertura.ser的ant脚本。因为当第一次运行cobertura时,会产生cobertura.ser,以后想屏蔽掉新增的测试类会不起作用,因为cobertura.ser老文件没有被删除会一直被沿用。
Ant代码 复制代码收藏代码
  1. <targetname="cleanup">
  2. <deletedir="${builddir}"/>
  3. <deletedir="${distdir}"/>
  4. <deletedir="${instrumenteddir}"/>
  5. <!--cleanuptheclassescoverageloginstrumentioninfo-->
  6. <deletefile="cobertura.ser"/>
  7. </target>
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics