测试覆盖率Emma工具使用
Emma使用与分析
#什么是Emma
EMMA 是一个开源、面向 Java 程序测试覆盖率收集和报告工具。它通过对编译后的 Java 字节码文件进行插装,在测试执行过程中收集覆盖率信息,并通过支持多种报表格式对覆盖率结果进行展示。 EMMA 所使用的字节码插装不仅保证 EMMA 不会给源代码带来“脏代码”,还确保 EMMA 摆脱了源代码的束缚,这一特点使 EMMA 应用于功能测试成为了可能。
#如何使用
emma现在可以通过命令行,ant,maven,Jenkins等方式使用,这里只介绍通过maven和Jenkins来集成emma测试。
##在Maven中的使用
直接运行mvn emma:emma,即可。
maven集成emma,需要两个插件,maven-surefire-plugin和emma-maven-plugin,如果之前没有安装,那么maven会自动下载这两个插件。
emma依赖于surefire的配置,默认执行src/test/java的junit测试。为了方便使用,最好在自己的pom里配置maven-surefire-plugin插件。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<skipTests>false</skipTests>
<junitArtifactName>junit:junit</junitArtifactName>
<includes>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/*_Roo_*</exclude>
</excludes>
</configuration>
</plugin>
这样指定maven-surefire-plugin的版本为2.8.1,false不跳过测试,/*Test.java只测试以Test.java为文件名结尾的文件,而且不测试/Roo文件名包含Roo的文件。更多的配置可以去查看maven-surefire-plugin的配置说明http://maven.apache.org/plugins/maven-surefire-plugin/。
##在Jenkins中的使用
在Jenkins系统管理的插件管理页面,添加Jenkins Emma plugin插件。
在项目配置中,加入emma:emma即可使用。

因为测试需要很长时间,而package命令会自动执行测试,所以有时候我们不想所有项目都测试。可以使用如下方案:系统配置两个分支,A分支用于开发,B分支用于上线。我们希望只要A分支进行emma测试,而在B分支不用测试方便快速上线。配置如下
在项目的pom.xml中,false默认不跳过测试,在B项目中配置clean -U compile package -Dmaven.test.skip=true,用来跳过测试。
##查看测试报告
###本地测试查看:
生成的报告是以html存储,默认的位置是${项目目录}/target/site/emma,打开index.html可以查看。
里面有类覆盖率,方法覆盖率,块覆盖率,行覆盖率,
选中其中的java文件还可以查看具体的代码覆盖率
绿色为有测试的,红色的是测试未覆盖的。
Jenkins 测试查看:
在项目主页中查看
这里会有项目的测试覆盖率曲线。x轴是版本变化,y轴是测试覆盖率。
点进图片进入本版本的详细测试报告。具体的形式和本地测试报告差不多,只是 jenkins测试报告没有具体的代码测试详情。
#工作原理
emma现在有两种工作方式,on-the-fly模式和offline模式:
On the fly 模式往加载的类中加入字节码,在程序运行中,用 EMMA 实现的classLoader 替代应用默认的 Custom classLoader,动态加载类文件,并向类中加入一些统计测试的字节码,这样运行结束后,测试数据也就通过这些临时加入的字节码分析出来。
Offline 模式在类被加载前,在编译生成的class文件中加入字节码。
On the fly 模式比较方便,缺点也比较明显:
它不能为被 boot class loader 加载的类生成覆盖率报告;而且,J2EE的classLoader和EMMA的classLoader都是同一类Custom classLoader,在j2ee项目启动过程中,必须选择应用容器(tomcat、Weblogic等等)相应的classLoader,从而无法使用emma的classLoader。同时,jenkins必须配合mvn的框架才能运行emma相关命令,而mvn框架只支持offline模式,所以如果想使用jenkins来做测试报告的话,就无法使用on the fly模式。在官方文档里也有说明:
As convenient as the on-the-fly mode is, in many cases it is not sufficient. For example, running a commercial J2EE container in a custom instrumenting classloader is practically impossible. Certain (bad) coding practices also fail for code executing in a custom classloader.
This on-the-fly instrumentation mode is handy for light-weight testing of main() test methods, individual classes, and small- to- mid-size programs. emmarun also works well with Swing applications.
这时,我们只能求助于 Offline 模式。下面用maven的运行方式来介绍一下。
通过在maven中执行,我们可以看出emma工作时主要运行以下几个步骤
- 字节码插装并生成插装的元信息文件coverage.em
- 运行测试
- 每次当 JVM 停止时,内存中记录的执行信息将被清除并被保存到 “coverage.ec” 的文件中。
- 生成测试报告。
##插装字节码
emma执行是最重要的就是插装字节码:
emma循环调用handleFile()方法来遍历目录下所有以'.class'结尾的文件,然后使用 classParser类得到要插装的组件
ClassDef class_table () throws IOException
{
m_table = new ClassDef ();
magic ();
version ();
if (DEBUG) System.out.println (s_line);
constant_pool ();
if (DEBUG) System.out.println (s_line);
access_flags ();
this_class ();
super_class ();
if (DEBUG) System.out.println (s_line);
//得到所有接口
interfaces ();
if (DEBUG) System.out.println (s_line);
//得到所有字段
fields ();
if (DEBUG) System.out.println (s_line);
//得到所有方法
methods ();
if (DEBUG) System.out.println (s_line);
//得到所有attribute
attributes ();
if (DEBUG) System.out.println (s_line);
return m_tabl e;
}
offline模式的插装会生成全新的class文件,默认放在target/generated-classes下。以下是原java文件和插装后的class反编译的java文件。
public class EmmaMain2 {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//junit调用publicTest()进行测试
public void publicTest(){
logger.info("this is a public method");
logger.info("我是分隔符------------------------------------------------------------------");
for(int i =1 ;i<10;i++){
privateTest();
if(i==4){
continue;
}
if(i==3){
break;
}
//永远不会执行到这一步,所以protectedTest()并没有被覆盖
if(i==5){
protectedTest();
return;
}
}
}
protected void protectedTest(){
logger.info("this is a protected method");
}
private void privateTest(){
logger.info("this is a private method");
}
}
插装字节码之后反编译的代码
public class EmmaMain2
{
private Logger logger = LoggerFactory.getLogger(getClass());
private static final boolean[][] $VRc;
private static final long serialVersionUID = -6204774612524021426L;
public EmmaMain2()
{
arrayOfBoolean[0] = true;
}
public void publicTest()
{
boolean[][] tmp3_0 = $VRc; if (tmp3_0 == null) tmp3_0; boolean[] arrayOfBoolean = $VRi()[1]; this.logger.info("this is a public method");
this.logger.info("我是分隔符------------------------------------------------------------------");
int i = 1; arrayOfBoolean[0] = true;
tmpTernaryOp = tmp3_0;
do
{
privateTest();
arrayOfBoolean[1] = true; if (i == 4) { arrayOfBoolean[2] = true;
} else
{
arrayOfBoolean[3] = true; if (i == 3) { arrayOfBoolean[4] = true;
break;
}
arrayOfBoolean[5] = true; if (i == 5) {
protectedTest(); arrayOfBoolean[6] = true;
return;
}
}
i++; arrayOfBoolean[7] = true; arrayOfBoolean[8] = true; } while (i < 10);
arrayOfBoolean[9] = true;
}
protected void protectedTest()
{
boolean[][] tmp3_0 = $VRc; if (tmp3_0 == null) tmp3_0; boolean[] arrayOfBoolean = $VRi()[2]; this.logger.info("this is a protected method");
arrayOfBoolean[0] = true;
}
private void privateTest()
{
boolean[][] tmp3_0 = $VRc; if (tmp3_0 == null) tmp3_0; boolean[] arrayOfBoolean = $VRi()[3]; this.logger.info("this is a private method");
arrayOfBoolean[0] = true;
}
static
{
boolean[] arrayOfBoolean = $VRi()[4];
arrayOfBoolean[0] = true;
}
private static boolean[][] $VRi()
{
boolean[][] tmp9_6 = (EmmaMain2.$VRc = new boolean[5]);
tmp9_6[0] = new boolean[1];
boolean[][] tmp15_9 = tmp9_6;
tmp15_9[1] = new boolean[10];
boolean[][] tmp22_15 = tmp15_9;
tmp22_15[2] = new boolean[1];
boolean[][] tmp28_22 = tmp22_15;
tmp28_22[3] = new boolean[1];
boolean[][] tmp34_28 = tmp28_22;
tmp34_28[4] = new boolean[1];
boolean[][] tmp40_34 = tmp34_28;
//将类信息加载到内存中。
RT.r(tmp40_34, "com/impulse/test/emma/EmmaMain2", -5598510326399570528L);
return tmp40_34;
}
}
反编译有些问题,但我们可以看出,emma在每个方法的入口和出口和转移指令之前如return、break、continue都加入了监测代码,并在最后把代码的执行情况通过RT.r()方法加载到内存的m_coverageMap中。
public static void r (final boolean [][] coverage, final String classVMName, final long stamp)
{
// note that we use class names, not the actual Class objects, as the keys here. This
// is not the best possible solution because it is not capable of supporting
// multiply (re)loaded classes within the same app, but the rest of the toolkit
// isn't designed to support this anyway. Furthermore, this does not interfere
// with class unloading.
final ICoverageData cdata = getCoverageData (); // need to use accessor for JMM reasons
// ['cdata' can be null if a previous call to dumpCoverageData() disabled data collection]
if (cdata != null)
{
synchronized (cdata.lock ())
{
// TODO: could something useful be communicated back to the class
// by returning something here [e.g., unique class ID (solves the
// issues of class name collisions and class reloading) or RT.class
// (to prevent RT reloading)]
cdata.addClass (coverage, classVMName, stamp);
}
}
}
public void addClass (final boolean [][] coverage, final String classVMName, final long stamp)
{
m_coverageMap.put (classVMName, new DataHolder (coverage, stamp));
}
所以当我们只测试publicTest()时,虽然publicTest()调用了protectedTest(),但由于我们通过条件语句的控制,使得protectedTest()永远不会被执行,因此在转移指令时加监控是必要的,我们可以在生成的报告中看出,
emma能够检测出那些虽然调用但没有执行到的代码。
##收集覆盖率信息
emma会检测jvm的运行情况,当通过命令行调用reset或者虚拟机停止(一般是测试完成时),emma会将测试的覆盖率信息通过 dumpCoverageData()方法导出成实体文件。默认为coverage-*.ec文件。
static void dumpCoverageData (final ICoverageData cdata, final boolean useSnapshot,
final File outFile, final boolean merge)
{
try
{
if (cdata != null)
{
// use method-scoped loggers everywhere in RT:
final Logger log = Logger.getLogger ();
final boolean info = log.atINFO ();
final long start = info ? System.currentTimeMillis () : 0;
{
final ICoverageData cdataView = useSnapshot ? cdata.shallowCopy () : cdata;
synchronized (Object.class) // fake a JVM-global critical section when multilply loaded RT's write to the same file
{
//在这里生者覆盖率信息文件,cdataView是CoverageData型,有一个重要的成员变量就是上面说的m_coverageMap
DataFactory.persist (cdataView, outFile, merge);
}
}
if (info)
{
final long end = System.currentTimeMillis ();
log.info ("runtime coverage data " + (merge ? "merged into" : "written to") + " [" + outFile.getAbsolutePath () + "] {in " + (end - start) + " ms}");
}
}
}
catch (Throwable t)
{
// log
t.printStackTrace ();
// TODO: do better chaining in JRE 1.4+
throw new RuntimeException (IAppConstants.APP_NAME + " failed to dump coverage data: " + t.toString ());
}
}
DataFactory.persist (cdataView, outFile, merge); cdataView是CoverageData型,有一个重要的成员变量就是上面说的m_coverageMap,没错,就是在这里把存在内存中的测试覆盖率信息持久化到文件中。
##生成测试报告
AbstractReportGenerator是个抽象工厂,根据参数不同而产生不同的 ReportGenerator。
public static IReportGenerator create (final String type)
{
if ((type == null) || (type.length () == 0))
throw new IllegalArgumentException ("null/empty input: type");
// TODO: proper pluggability pattern here
if ("html".equals (type))
return new com.vladium.emma.report.html.ReportGenerator ();
else if ("txt".equals (type))
return new com.vladium.emma.report.txt.ReportGenerator ();
else if ("xml".equals (type))
return new com.vladium.emma.report.xml.ReportGenerator ();
else // TODO: error code
throw new EMMARuntimeException ("no report generator class found for type [" + type + "]");
}

public
abstract class AbstractItemVisitor implements IItemVisitor
{
// public: ................................................................
//概要覆盖信息
public Object visit (final AllItem item, final Object ctx)
{
return ctx;
}
//包测试覆盖信息
public Object visit (final PackageItem item, final Object ctx)
{
return ctx;
}
//源文件测试覆盖信息
public Object visit (final SrcFileItem item, final Object ctx)
{
return ctx;
}
//在html中没有
public Object visit (final ClassItem item, final Object ctx)
{
return ctx;
}
//在html没有
public Object visit (final MethodItem item, final Object ctx)
{
return ctx;
}
}
三种ReportGenerator都实现了IReportGenerator接口的process方法来到处报告,而process方法又分别调用了各种重载的visit()方法。当maven生成html测试报告是,只用了生成概要覆盖信息、源文件测试覆盖信息、包测试覆盖信息的方法。
参考资料
测试覆盖率Emma工具使用的更多相关文章
- 测试覆盖率工具EclEmma安装与使用
此文来自于:https://www.cnblogs.com/cnsdhzzl/p/7638883.html EclEmma的简介 一个优秀的开源软件测试工具 eclipse的一个插件 能够对由 Jav ...
- 测试覆盖率工具:EclEmma
测试覆盖率工具:EclEmma 2016-08-26 目录 1 测试覆盖率实现技术2 EclEmma介绍3 EclEmma测试覆盖率指标4 EclEmma安装5 示例项目介绍 5.1 创建项目 5 ...
- iOS 覆盖率检测原理与增量代码测试覆盖率工具实现
背景 对苹果开发者而言,由于平台审核周期较长,客户端代码导致的线上问题影响时间往往比较久.如果在开发.测试阶段能够提前暴露问题,就有助于避免线上事故的发生.代码覆盖率检测正是帮助开发.测试同学提前发现 ...
- 精准测试与开源工具Jacoco的覆盖率能力大PK
导读:本文根据实际使用情况,简要分析了精准测试和类Jacoco等传统白盒工具在设计理念.功能和应用场景的异同点,并阐述了覆盖率技术如何在新型企业开发体系中,发挥应有的重要作用. 覆盖率技术可以说是测试 ...
- [Node.js] Express的测试覆盖率
原文地址:http://www.moye.me/2014/12/03/express_coverage/ 引子 有群友问到Express怎么做 单元测试/覆盖率测试,这是上篇所遗漏的,特此补上 Exp ...
- 使用 coverlet 查看.NET Core应用的测试覆盖率
代码覆盖(Code coverage)是软件测试中的一种度量,描述程式中源代码被测试的比例和程度,所得比例称为代码覆盖率. Visual Studio 2017的企业版可以直接查看测试的代码覆盖率, ...
- 用 Cobertura 测量测试覆盖率
尽管测试先行编程(test-first programming)和单元测试已不能算是新概念,但测试驱动的开发仍然是过去 10 年中最重要的编程创新.最好的一些编程人员在过去半个世纪中一直在使用这些 ...
- 让 Python 代码更易维护的七种武器——代码风格(pylint、Flake8、Isort、Autopep8、Yapf、Black)测试覆盖率(Coverage)CI(JK)
让 Python 代码更易维护的七种武器 2018/09/29 · 基础知识 · 武器 原文出处: Jeff Triplett 译文出处:linux中国-Hank Chow 检查你的代码的质 ...
- 白盒测试笔记之:Junit 单元测试以及测试覆盖率
单元测试: 单元测试是从代码层面验证代码的正确性. 一般考虑接口中的数据结构是否正确,变量的临界条件,如空字符串,空集合等. Junit入门 参考: Junit 入门教程: https://ww ...
随机推荐
- Failed to check the status of the service报错解决
报这个错误是因为我的application_context.service.xml 文件里的的dubbo声明暴露口时的ref属性写错了. <dubbo:service interface=&qu ...
- SG函数学习总结
有点散乱, 将就着看吧. 首先是博弈论的基础, 即 N 和 P 两种状态: N 为必胜状态, P 为必败状态. 对于N, P两种状态, 则有 1. 没有任何合法操作的状态, P; 2. 可以移动到P局 ...
- Ubuntu 16.04下使用Wine安装正则表达式工具RegexBuddy 4
说明: 1.使用的Wine版本是深度出品(Deepin),已经精简了很多没用的配置,使启动能非常快,占用资源小. 2.关于没有.wine文件夹的解决方法:在命令行上运行winecfg: 下载: (链接 ...
- Java获取当前时间戳/时间戳转换
时间戳精度有两个概念:1是精确到秒,2是精确到毫秒. 要操作时间戳和时间戳转换为时间一般对应的对象就是Date,而Date各种转换离不开SimpleDateFormat: 如果是要获取时间指定的年月日 ...
- Java中的文件上传2(Commons FileUpload:commons-fileupload.jar)
相比上一篇使用Servlet原始去实现的文件上传(http://www.cnblogs.com/EasonJim/p/6554669.html),使用组件去实现相对来说功能更多,省去了很多需要配置和处 ...
- Android Framework 记录之一
简介 之前的研究太偏向应用层功能实现了,很多原理不了解没有深究,现在研究framework框架层了. 记录 1.下载源码,目录如下: 2.Android系统的层次如下: 3.项目目录简单分析如下: 4 ...
- 使用zerorpc踩的第一个坑:
Server端代码:注意s.run() 和 s.run的区别,一个括号搞死我了.如果不加括号,服务端服务是不会启动的,客户端就会报连接超时的错误 Server端在本机所有IP上监听4242端口的tcp ...
- 禁用Clusterware在系统启动后自己主动启动
以下是禁用及启用Clusterware随系统启动而自己主动启动的方法 10g下我们用例如以下方法: 禁用Clusterware随系统启动而自己主动启动 /etc/init.d/init.crs dis ...
- Swift----编程语言语法
1 简单介绍 今天凌晨Apple刚刚公布了Swift编程语言,本文从其公布的书籍<The Swift Programming Language>中摘录和提取而成.希望对各位的iOS&a ...
- Solaris设备管理
接手一台服务器,如何了解它的软硬件配置 数据库可以装在裸设备上,何为裸设备 知识点: 内核 驱动程序 设备命名 设备访问方式 磁盘的管理 内核: 指挥硬件干活的工具,shell翻译官,将人类语言翻译为 ...