how to do java application’s code coverage with emma?

本文主要讨论如何用emma给Java Application做代码覆盖,区别于java单元测试的code coverage, Java Application一直处于运行状态,所以不能暴力的强行kill进程来收集代码覆盖数据,所以本文梳理下网络上的相关文章,整理下过程,并记录下尝试过程中一些常见问题的解决:

基本步骤分为五步:

(1)修改编译选项:

将编译的选项设置为debug启用,同时要注意debug的level.例如maven compiler默认就启用了debug但是没有将debug的level都设置进去,所以这里注意设置启用和启用的级别。例如对于maven项目中的compile plugin: (如果使用了progard进行混淆,则去掉混淆)

<configuration>
<debug>true</debug>
<debuglevel>lines,source</debuglevel>
</configuration>

(2) 注入:
注入coverage信息,可以使用-ix来过滤指定包:
java emma instr -m overwrite -cp server.jar -ix +com.server* -Dmetadata.out.file=coverage.em

note:

2.1 其中在 “+” 符号后的文件为包含进的文件, “-” 后面的内容为排除在外的文件,例如

-ix +com.foo.*,-com.foo.test.*,-com.foo.*Test*

2.2 要支持EMMA,需要复制emma.jar到lib目录,记得修改权限,例如:jdk1.7.0_60/jre/lib/ext/emma.jar

2.3 有时候加上过滤条件导出并没有生效,显示过滤的文件少了,但是产生的coverage.em基本没有变,可以删掉原先的,估计有merge操作。

(3)修改应用启动的jvm参数并启动应用:

3.1 修改启动应用的jvm参数:
-Demma.rt.control=true

为什么要增加这项,可以查看代码:默认是不启动实时收集功能。

IProperties appProperties = getAppProperties();
if (appProperties != null) {
String property = appProperties.getProperty("rt.control", "false");
enableController = Property.toBoolean(property);
}

if (enableController) {
int port = 47653;

if (appProperties != null) {
String property = appProperties.getProperty("rt.control.port");
if (property != null) {
try {
port = Integer.parseInt(property);
} catch (NumberFormatException ignore) {
System.err
.println("ignoring malformed [rt.control.port] value: "
+ property);
}

ps: 还存在其他很多参数,例如-Demma.coverage.out.file=/var/coverage.ec控制输出文件,-Demma.rt.control.port=12345,用来控制端口

3.2 启动,并查看log来确认是否正常:

应该存在2行log:其中第二行对应3.1要解决的问题。

EMMA: collecting runtime coverage data ...
EMMA: runtime controller started on port [47653]

其中,如果启动失败并提示class format错误,这是应用是JDK1.7编译的,而之前注入使用的emma的注入方式应该是不兼容的,所以可以通过增加JVM启动参数来解决:
1.7使用-XX:-UseSplitVerifier  如果是1.8使用-Xverify:none

(4)执行测试用例,然后使用命令收集数据:

java emma ctl -connect localhost:47653 -command coverage.get,coverage.ec

假设需要合并之前的,执行命令:

java emma merge -input coverage1.ec,coverage2.ec -out coverage.ec

PS: 也可以支持em的Merge.

(5)结合(2)产生的em和(4)产生的ec文件以及源代码的路径产生html报告:

java emma report -r html -sp /xxxx/xxxx/src/main/java -in coverage.em,coverage.ec -Dreport.html.out.file=coverage.html

这里需要注意的是源代码要和产生的统计信息一致,否则假设源代码当中的一个文件已经重命名,那么在html报告中还是提示找不到源文件。

实际测试中,我们还会应用单元测试,产生覆盖率,然后可以与上述产生的覆盖率合并。-in 支持传多个ec,em.

对于单元测试如何产生覆盖数据,可以直接配置EMMA的maven插件,然后使用mvn emma:emma来产生在target目录:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-XX:-UseSplitVerifier</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>emma-maven-plugin</artifactId>
<version>1.0-alpha-3</version>
</plugin>

 

PS:

通过上面可知,对于Coverage数据的过滤只有在注入阶段,在报告阶段并没有,好在本人同事对emma.jar进行了二次开发(emma_rar),支持在上面的第五步Report阶段过滤Class/Package。

具体命令:

java emma report -r html  -props filter.txt   -sp /xxxx/xxxx/src/main/java -in coverage.em,coverage.ec -Dreport.html.out.file=coverage.html

filter.txt 文件内容的格式可以是report.html.filter.class或者report.html.filter.package,例如示例:

report.html.filter.class=com.Tp.java,com.Constants.java   //多个类用,分割。

这里一定要注意的是:

(1)如果是内部类的过滤,一定不要带上外部类的名字,比如不应该是outer.inner.java,而应该是inner.java.这个可以查看EM文件的内容获知。

(2)支持正则表达式。

Notes:

(1) debug log: -Dverbosity.level=trace1

以上即为整个过程的记录以备忘!