原文链接:https://blog.csdn.net/kittyboy0001/article/details/25078449
Cobertura原理
Cobertura 是一种开源工具,它通过检测基本的代码,并观察在测试包运行时执行了哪些代码和没有执行哪些代码,来测量测试覆盖率。
官方地址:http://sourceforge.net/projects/cobertura/
日本下载:http://sourceforge.jp/projects/sfnet_cobertura/releases/
Cobertura 在 Maven 编译平台上有相应的 cobertura-maven-plugin 插件,使代码编译、检测、集成等各个周期可以流水线式自动化完成。
插件地址:http://mojo.codehaus.org/cobertura-maven-plugin/
Cobertura原理及过程
cobertura执行过程大致如下:
1,使用instrument修改我们编译后的class文件。
2,执行测试,测试数据输出到xxx.ser中。
3,使用report生成覆盖率报告。
1、instrument
cobertura使用instrument修改我们编译后的class文件,在代码里面加入cobertura的统计代码。并生成一个.ser文件(用于覆盖率数据的输出)。
在使用instrument执行的过程中,CoberturaInstrumenter会首先调用分析监听器分析给定的编译好的.class,获得touchPoint(可以认为对应于源代码中的待覆盖行)以及需要的其他信息。然后调用注入监听器将信息注入到新的.class中,保存到\target\generated-classes目录下。
demo的源代码如下(裁剪并只保留留下类的代码):
public class FeeCodeTranslate {
private static final Map<String, String> FEECODEMAP = new HashMap<String, String>();
static{
FeeCodeTranslate.FEECODEMAP.put("机架费用合计", "JJFY");
FeeCodeTranslate.FEECODEMAP.put("带宽费用合计", "DKFY");
FeeCodeTranslate.FEECODEMAP.put("光纤租用费用", "CSFY");
}
public static String nameToCode(String name){
String code = FeeCodeTranslate.FEECODEMAP.get(name);
return code == null ? "" : code;
}
}
源代码生成.class文件后,反编译如下:
public class FeeCodeTranslate {
private static final Map FEECODEMAP;
public FeeCodeTranslate() //增加了构造函数
{
}
public static String nameToCode(String name){
String code = (String)FEECODEMAP.get(name);
return code != null ? code : "";
}
static {
FEECODEMAP = new HashMap();
FEECODEMAP.put("机架费用合计", "JJFY");
FEECODEMAP.put("带宽费用合计", "DKFY");
FEECODEMAP.put("光纤租用费用", "CSFY");
}
}
使用instrument执行后,得到新的.class如下:
public class FeeCodeTranslate {
public static final transient int __cobertura_counters[]; //增加了计数数组
private static final Map FEECODEMAP;
public FeeCodeTranslate()
{
int i = 0;
__cobertura_counters[1]++; //增加了计数代码
super();
}
public static String nameToCode(String name){
String code;
__cobertura_init(); //增加了计数代码,初始化计数代码
int i = 0;
__cobertura_counters[7]++; //增加了计数代码
code = (String)FEECODEMAP.get(name);
__cobertura_counters[8]++; //增加了计数代码
code;
int j = 10;
JVM INSTR ifnonnull 67;
goto _L1 _L2
_L1:
__cobertura_counters[9]++; //增加了计数代码
j = 0;
"";
goto _L3
_L2:
__cobertura_counters[j]++; //增加了计数代码
j = 0;
code;
_L3:
return;
}
public static void __cobertura_init() { //增加了计数代码
if (__cobertura_counters == null) {
__cobertura_counters = new int[11];
TouchCollector.registerClass("com/baidu/idc/base/FeeCodeTranslate");
}
}
//增加了计数代码
public static void __cobertura_classmap_0(LightClassmapListener lightclassmaplistener) {
lightclassmaplistener.putLineTouchPoint(11, 1, "<init>", "()V");
lightclassmaplistener.putLineTouchPoint(13, 2, "<clinit>", "()V");
lightclassmaplistener.putLineTouchPoint(15, 3, "<clinit>", "()V");
lightclassmaplistener.putLineTouchPoint(16, 4, "<clinit>", "()V");
lightclassmaplistener.putLineTouchPoint(17, 5, "<clinit>", "()V");
lightclassmaplistener.putLineTouchPoint(18, 6, "<clinit>", "()V");
lightclassmaplistener.putLineTouchPoint(21, 7, "nameToCode", "(Ljava/lang/String;)Ljava/lang/String;");
lightclassmaplistener.putLineTouchPoint(22, 8, "nameToCode", "(Ljava/lang/String;)Ljava/lang/String;");
lightclassmaplistener.putJumpTouchPoint(22, 10, 9);
LightClassmapListener = lightclassmaplistener;
}
//增加了计数代码
public static void __cobertura_classmap( LightClassmapListener lightclassmaplistener) {
lightclassmaplistener.setClazz("com/baidu/idc/base/FeeCodeTranslate");
lightclassmaplistener.setSource("FeeCodeTranslate.java");
__cobertura_classmap_0(lightclassmaplistener);
LightClassmapListener = lightclassmaplistener;
}
//增加了计数代码
public static int[] __cobertura_get_and_reset_counters(){
int ai[] = __cobertura_counters;
__cobertura_counters = new int[__cobertura_counters.length];
return ai;
}
static {
__cobertura_init(); //增加了计数代码
int i = 0;
__cobertura_counters[2]++; //增加了计数代码
FEECODEMAP = new HashMap();
__cobertura_counters[3]++; //增加了计数代码
FEECODEMAP.put("机架费用合计", "JJFY");
__cobertura_counters[4]++; //增加了计数代码
FEECODEMAP.put("带宽费用合计", "DKFY");
__cobertura_counters[5]++; //增加了计数代码
FEECODEMAP.put("光纤租用费用", "CSFY");
__cobertura_counters[6]++; //增加了计数代码
}
}
2、执行测试
为了在测试用例执行时,调用cobertura修改过的.class命令。classpath设置时,instrumented classes 要在original (uninstrumented) classes前面。这样测试时就会引用cobertura修改过的.class。测试信息写入到cobertura.ser档案文件。
命令执行时需要注意的事项,参见如下ant中的片段:
<target name="test" depends="init,compile">
<junit fork="yes" dir="${basedir}" failureProperty="test.failed">
<!--
Specify the name of the coverage data file to use.
-->
<sysproperty key="net.sourceforge.cobertura.datafile" file="${reports.dir}/coverage.ser" />
<!--
Note the classpath order: instrumented classes are before the
original (uninstrumented) classes. This is important.
-->
<classpath location="${instrumented.dir}" />
<classpath location="${classes.dir}" />
<!--
The instrumented classes reference classes used by the
Cobertura runtime, so Cobertura and its dependencies
must be on your classpath.
-->
<classpath refid="cobertura_classpath" />
<formatter type="xml" />
<test name="${testcase}" todir="${reports.xml.dir}" if="testcase" />
<batchtest todir="${reports.xml.dir}" unless="testcase">
<fileset dir="${src.dir}">
<include name="**/*Test.java" />
</fileset>
</batchtest>
</junit>
<junitreport todir="${reports.xml.dir}">
<fileset dir="${reports.xml.dir}">
<include name="TEST-*.xml" />
</fileset>
<report format="frames" todir="${reports.html.dir}" />
</junitreport>
</target>
3、生成报告
从cobertura.ser获取覆盖率数据,然后结合src中提供的源代码,生成最终的覆盖率报告,放到了target\site\cobertura路径下。
java -cp "%COBERTURA_HOME%cobertura.jar;..."
net.sourceforge.cobertura.reporting.Main --format html
--datafile cobertura.ser --destination cobertura-site src
最终,得到覆盖率报告数据如下图所示: