關鍵字:測試覆蓋率工具
就在幾個月前的一個項目中,我決心按照敏捷開發的實踐,為項目組引入一個測試覆蓋率工具。首先考慮的當然是大名鼎鼎的Clover,Clover功能強勁,理應是測試覆蓋率的首選工具。但Clover用于商業開發是要收費的,況且我喜歡開源、免費的東西。經過了一番測試和比較,我選擇了EMMA Code Coverage。就像一篇評測覆蓋率工具的Blog所言,它是開源工具的首選。 在討論EMMA的使用之前,我首先簡要介紹幾個相關的概念。
測試覆蓋率(Code Coverage)
測試覆蓋率,簡單的說,就是評價測試活動覆蓋產品代碼的指標。測試的目的,是確認產品代碼按照預期一樣工作,也可以看作是產品代碼工作方式的說明文檔。進一步考慮,測試覆蓋率可以看作是產品代碼質量的間接指標--之所以說是間接指標,因為測試覆蓋率評價的是測試代碼的質量,并不是產品代碼的質量。
代碼覆蓋率是一種白盒測試,因為測試覆蓋率是評價產品代碼類內部的指標,而不是評價系統接口或規約。測試覆蓋率尤其用于評價測試代碼是否已經覆蓋了產品代碼所有的路徑。
衡量測試覆蓋率的指標很多,常用的指標有:
Statement coverage,也稱作Line coverage,用于評價測試的代碼語句覆蓋率。
Basic block coverage,是Statement coverage的一個變種,它把沒有一個分支的代碼區域作為一個計量單位,而不是簡單的代碼行,用于一個if-else分支代碼行數遠遠大于另一個的情況,在這種情況下,statement coverage指標并不適用。
Decision coverage(也稱作Branch coverage),用于評價代碼分支地測試覆蓋率。
Path coverage,和Decision coverage相似,用于評價代碼從開始到結束所有路徑的測試覆蓋率。
Function coverage,用于評價代碼方法的測試覆蓋率。
EMMA目前支持四種Coverage類型:class、method、line和basic block。
測試覆蓋率的實現方式
實現測試服務覆蓋率的技術通常分為兩種:
1、Instrumentation
Instrumentation技術在產品代碼的關鍵位置插入統計代碼。事實上,Instrumentation技術可以分為兩種方式:Class Instrumentation和Source Instrumentation。前者把統計代碼插入編譯好的.class文件,而后者則把統計代碼插入源代碼并編譯成新的.class文件。大多數測試覆蓋率工具采用這兩種Instrumentation技術。
2、Custom JVM
另一種方式是在JVM中把統計代碼插入.class。測試覆蓋率分析可以在JVM執行測試代碼的過程中完成。
測試覆蓋率工具的典型特性
1、和Ant集成
2、多種報告輸出格式
3、源代碼鏈接
4、覆蓋率歷史報告
EMMA的特點
Instrumentatiton方式:EMMA使用兩種模式來實現覆蓋率的統計,它稱作“offline”和“on-the-fly”。EMMA使用Instrumentation .class文件的方式。EMMA通過byte instrumentation生成.class文件的增強版本,加入了統計測試覆蓋率的代碼Hook。對于“offline”模式,它從硬盤讀入.class文件,然后輸出經過Instrumented的增強版本;對于“on-the-fly”模式,這些操作發生在JVM內部(即增強版本的.class文件不寫入硬盤)。前者是通用的模式,而后者用于簡單的Java應用程序。
支持的覆蓋率指標:EMMA支持class,method,line和basic block coverage指標。
優越的性能和可伸縮性。
Java平臺支持:EMMA支持Java 1.2或更高版本的JVM,不依賴于任何第三方類庫。
CPL License。
使用EMMA:命令行方式
安裝EMMA的jar文件到類路徑-最簡單的方法是,把emma.jar復制到/lib/ext/。注意,不要復制emma_ant.jar,否則使用Ant腳本會出錯。
on-the-fly模式:使用emmarun-使用-g選項編程java源代碼,javac -g -d out ,然后執行java emmarun -cp out 。本方法把instrumentation和執行過程合而為一。
offline模式:分開instrumentation過程和執行過程-首先使用-g選項編譯java源代碼,javac -g -d out ;然后是instrumentation,java emma instr -d outinstr -ip out,注意,經過instrumentation的class目標目錄是outinstr;最后是執行過程,java -cp outinstr;out ,注意,把經過instrumentation的類路徑放在前面,并在后面加上原來的類路徑,因為instr命令沒有處理properties文件和interface,這些都是執行過程需要的。
使用EMMA:Ant
設置instrumentation屬性
<property name="coverage.dir" value="${basedir}/coverage" />
<property name="out.instr.dir" value="${basedir}/outinstr" />
<property name="emma.enabled" value="true" />
<property name="javac.debug" value="on" />
<!-- path element used by EMMA taskdef below: -->
<path id="emma.lib" >
<pathelement location="${libs}/emma.jar" />
<pathelement location="${libs}/emma_ant.jar" />
</path>
在Ant腳本中加入EMMA task
<!-- this loads <emma> and <emmajava> custom tasks: -->
<taskdef resource="emma_ant.properties" classpathref="emma.lib" />
在編譯Task中打開debug選項
<target name="compile">
<mkdir dir="${classes.main}" />
<javac srcdir="${src.main}" destdir="${classes.main}" debug="${javac.debug}">
<classpath refid="classpath.lib" />
</javac>
<copy todir="${classes.main}">
<fileset dir="${src.main}" includes="**/*.xml, **/*.vm" />
</copy>
</target>
instrumentation task
<target name="instrument" depends="compile">
<mkdir dir="${out.instr.dir}" />
<mkdir dir="${coverage.dir}" />
<emma enabled="${emma.enabled}" >
<instr instrpathref="classpath.main"
destdir="${out.instr.dir}"
metadatafile="${coverage.dir}/metadata.emma"
merge="true"
>
<filter excludes="com.talent.fw.formula.test.*Test*,com.talent.fw.esb.*Test*,com.talent.fw.message.test.*,com.
talent.fw.entityengine.*Test*,com.talent.fw.integration.*Test*,com.talent.fw.security.impl.*Test*,testdomain.*" />
</instr>
</emma>
</target>
JUnit測試
<target name="test" depends="compile, instrument">
<mkdir dir="${reports.junit.data}" />
<mkdir dir="${classes.main}/maps" />
<mkdir dir="${classes.main}/scripts" />
<copy todir="${classes.main}/maps">
<fileset dir="${src.main}/maps" />
</copy>
<copy todir="${classes.main}/scripts">
<fileset dir="${src.main}/scripts" />
</copy>
<copy todir="${classes.main}">
<fileset dir="${src.main}" includes="*.xml, *.properties, **/*.vm, **/*.dtd" />
</copy>
<rmic classname="com.talent.fw.message.test.RMITestServer" base="${classes.main}"/>
<junit printsummary="yes" haltonfailure="no" failureproperty="tests.failed">
<classpath location="${out.instr.dir}" />
<classpath location="${classes.main}" />
<classpath location="${src.main}" />
<classpath refid="classpath.lib" />
<jvmarg value="-Demma.coverage.out.file=${coverage.dir}/coverage.emma" />
<jvmarg value="-Demma.coverage.out.merge=true" />
<formatter type="xml" />
<batchtest fork="yes" todir="${reports.junit.data}" failureproperty="tests.failed">
<fileset dir="${classes.main}">
<include name="**/*Test.class" />
<exclude name="**/AllTests.class" />
<exclude name="**/Base*Test.class" />
</fileset>
</batchtest>
</junit>
</target>
生成報告并復制到Tomcat的發布目錄下
<target name="coverage.report" depends="instrument">
<!-- if enabled, generate coverage report(s): -->
<emma enabled="${emma.enabled}" >
<report sourcepath="${src.main}"
sort="+block,+name,+method,+class"
metrics="method:70,block:80,line:80,class:100"
>
<fileset dir="${coverage.dir}" >
<include name="*.emma" />
</fileset>
<!-- <xml outfile="${coverage.dir}/coverage.xml" depth="package" /> -->
<html outfile="${coverage.dir}/coverage.html" depth="method"
columns="name,class,method,block,line"
/>
</report>
</emma>
<mkdir dir="${coverage.publish.dir}" />
<copy todir="${coverage.publish.dir}">
<fileset dir="${coverage.dir}">
<include name="**/*.html" />
</fileset>
</copy>
</target>
和CruiseControl集成
最簡單的集成方式是在cruisecontrol的navigation.jsp文件下方加入EMMA測試覆蓋率報告的超鏈接。如下圖:

進一步的集成方式是,在生成HTML報告的同時,生成XML格式的報告,并為XML格式報告編寫XSL文件,并加入到CruiseControl的buildresults.jsp文件中。
延伸閱讀
文章來源于領測軟件測試網 http://www.kjueaiud.com/