转载,地址:http://qa.taobao.com/?p=6425
从Instrument的方式比较Emma与Clover
2010年5月19日 由 yuanpu 留言 »1 INTRODUCTION
最近在做与code coverage相关的工作,接触了一些code coverage 的知识。在这里与大家做个分享。下文主要是在Instrumentation的方式上,对Emma与Clover进行了比较,并且分析了由于Instrumentation方式上的不同而导致的coverage的结果不同的原因。
关于clover和emma,是两个大家都厂家的工具,这里就不多说了。仅给出链接,供详细了解。
Clover: http://www.atlassian.com/software/clover/
Emma: emma.sourceforge.net
注:本文所说的代码仅局限在Java代码上。
2 INSTRUMENTATION 方式介绍
在这一章中,我们大致的介绍一下Instrumentation的不同的方式。
为了获得code coverage measurement, 目前大致有三种Instrumentation的方式:
源代码的Instrumentation(source code instrumentation): 这种方式在源代码上,直接进行Instrumentation. 然后将Instrument后的代码编译成.class 文件。
中间代码的Instrumentation(Intermediate code instrumentatio): 这种方式是直接在编译后的.class文件是直接加入新的字节码。
实时信息的获取( Runtime Information Collection):这种方式是在代码运行过程中收集动态的运行消息而决定coverage的信息。
目前Clover采用的是第一种方式(源代码的Instrumentation),而Emma采用的是第二种方式(中间代码的Instrumentation)。
3 CLOVER的INSTRUMENTATION 方式
在这一章中,我们将详细描述一下源代码的Instrumentation的原理。就拿图一的代码做个例子. (图一的代码功能及其简单,这里就不多说了)。
图一:before Instrumentation
我们再看看Instrument以后的代码(见图二)。
图二: After instrumentation
由图一和图二的对比,我们可以发现,在每一个method, statement和branch之前,都添加了一行代码。这行代码代表着是一个整形数的counter.
特别的,如果针对branch,我们要区分是“true”还是”false”的branch被执行了,所以做了特别的处理。如图二中所示,if 后的Boolean expression变成了
if ( ( data==0 && ++CT[338]!=0 | true ) || ( ++CF[338] == 0 & false ) )
如果data==0是true, 那么 && 之后的部分也会执行。 这样CT[338]就会被增加一。而最后的“|true”是为了保证整个的boolean expression的值还是保证为”true”.
如歌data==0是false,那么“||”之后的部分会被执行。这样CF[338]就会被增加一。而最后的“&false”是为了保证整个的boolean expression的值还是保证为”false”.
从以上的Instrument的方式,我们可以看出,每次在源代码中加入一个Counter, 我们都需要一个整形数(4 bytes)。 这样对于一个大型的项目,开销还是非常大的!
4 EMMA 的INSTRUMENTATION方式
至于Emma 的Instrumentation 的方式,可以参见图三。
图三:Instrumentation in Emma.
如图三所示,Emma通过对.class文件中的bytecode进行分析, 从而找出在.class文件中每一个basic block. 并且在每个basic block之后插入一个Probe(探针)。 这样每个probe被执行后,程序运行时coverage的信息也就被收集到了。
在这里每一个basic block是指一段没有保护任何跳转指令的bytecode 指令。 而每个probe是指一段用于记录某个basic block是否被运行的bytecode. 具体的每个probe包含以下四条指令(或者是类似这四条的指令):
1. ALOAD probearray #在emma 会在每个class中新建一个boolean的数组,用于记录每个basic block是否被执行。这行指令,就是把这个数组的引用加载进操作数栈
2. ICONST probeid #将某个probe的id, 加载进操作数栈
3. ICONST_1 #将常量”1”加载进操作数栈
4. BASTORE #将probearry[probeid] 的值改为1.
至于 emma的内存开销,主要也就是花费在每个class所开的boolean数组(1 byte per element)。 而这个数组的大小是这个class内的basic block的数量决定的。
5 不同INSTRUMENTATION 方式所得到的结果
那么由这两种不同的Instrumenetation方式得到的coverage的结果又会有什么不同呢。当然正常情况下,clover 和emma基本类似。但是遇到特别的情况就不一样了。
就拿下面的程序做个例子, 我们给coverageTest()的输入是0, 因此期望只执行” (a==0)? (data+1): (data+2);”中的一个分支
public static void main(String[] args) {
int a = 0;
coverageTest( a );
}
public static void coverageTest( int a ){
int data = 0;
a = (a==0)? (data+1): (data+2);
}
在clover 下运行的结果如下图所示,全部被覆盖:Line coverage: 100%
在emma下的结果如下图所示:
上图显示“(a==0)? (data+1): (data+2);”为黄色。这个在emma中表示的意思是这行代码没有全部被覆盖。特别的, line coverage为92% (2.8/3)。这个背后的原因是“(a==0)? (data+1): (data+2);”在byte code level被分成不同的basic block来处理。
6 结论
上文简单的就clover和emma的不同的instrumentation的方式进行了讨论,并且举例说明了不同的instrumentation方式对所得到coverage的准确度的影响。显而易见,emma是能更加准确的反应代码的覆盖情况的。
当然这两种Instrumentataion 方式还有很多不同,还没深入研究,这里就不敢多说了。
最后从Clover的官方网站上截取了一段话给大家看看:
Clover uses source code instrumentation, because although it requires developers to perform an instrumented build, source code instrumentation produces the most accurate coverage measurement for the least runtime performance overhead.