使用 EMMA 測量測試覆蓋率
本文主要通過一個示例項目介紹如何在集成了 Ant 和 Junit 的基礎上,利用 EMMA 來收集 單元測試 對代碼的覆蓋率。 介紹測試代碼覆蓋率的重要性 測試驅動 開發 ( TDD )是 極限編程 的一個重要特點,它具有很多優點,并被越來越多的開發人員所接受。在 測試驅
本文主要通過一個示例項目介紹如何在集成了 Ant 和 Junit 的基礎上,利用 EMMA 來收集
單元測試對代碼的覆蓋率。
介紹測試代碼覆蓋率的重要性
測試驅動開發(TDD)是極限編程的一個重要特點,它具有很多優點,并被越來越多的開發人員所接受。在測試驅動開發過程中,程序員經歷了編寫測試用例,實現功能,重構代碼這個不斷迭代的過程。實踐證明,這個過程能顯著提高我們的生產效率,并產生高質量的代碼。它還能給我們以自信,讓我們放心的重構自己的代碼。
測試代碼確實能夠保證代碼的質量,但如果你以為自己已經寫了一堆測試用例,并都能運行通過時,就能高枕無憂了,那么你錯了。隱藏的 Bug 也許只是在等待時機讓你的系統崩潰。這是什么原因呢?聰明的你肯定已經想到,測試代碼是用來保證功能代碼的質量的,但測試代碼的質量如何,我們不得而知。我們需要知道,我們辛苦編寫的測試代碼到底覆蓋了多少功能代碼,這就是我寫這篇文章的出發點,我將介紹一種測試代碼覆蓋率的工具 - EMMA。


|
回頁首 |
|
介紹 EMMA
EMMA 是一個用于檢測和報告 JAVA 代碼覆蓋率的開源工具。它不但能很好的用于小型項目,很方便得得出覆蓋率報告,而且適用于大型企業級別的項目。
EMMA 有許多優點,首先你能免費得到它,并把它用于自己項目的開發。它支持許多種級別的覆蓋率指標:包,類,方法,語句塊(basic block)和行,特別是它能測出某一行是否只是被部分覆蓋,如條件語句短路的情況。它能生成 text,xml,html 等形式的報告,以滿足不同的需求,其 html 報告提供下鉆功能,我們能夠從 package 開始一步步鏈接到我們所關注的某個方法。EMMA 能和 Makefile 和 Ant 集成,便于應用于大型項目。特別還須指出的一點是,EMMA 的效率很高,這對于大型項目來說很重要。
EMMA 是通過向 .class 文件中插入字節碼的方式來跟蹤記錄被運行代碼信息的。EMMA 支持兩種模式:On the fly 和 Offline 模式。
On the fly 模式往加載的類中加入字節碼,相當于用 EMMA 實現的 application class loader 替代原來的 application class loader。
Offline 模式在類被加載前,加入字節碼。
On the fly 模式比較方便,缺點也比較明顯,如它不能為被 boot class loader 加載的類生成覆蓋率報告,也不能為像 J2EE 容器那種自己有獨特 class loader 的類生成覆蓋率報告。這時,我們能求助于 Offline 模式。
EMMA 也支持兩種運行方式:Command line 和 Ant。
命令行一般和 On the fly 模式一起適用,對于簡單的項目能夠快速產生覆蓋率報告。通過 Ant task 來運行 EMMA 的話,特別適用于大型的項目。
本文后面提供的實例主要是演示如何集成 EMMA 和 Ant,通過 Offline 模式產生覆蓋率報告。


|
回頁首 |
|
示例項目
示例工程 SampleProject 是個小型的項目,有一個類 NumberParser,主要功能是把一個字符串解析成 float 型。下面是整個工程的目錄結構。
圖1. 示例項目的目錄結構
下面,我們開始來為我們的工程編寫 Ant 腳本。
清單1設置一些屬性,包括源文件,二進制文件,JUnit 報告,覆蓋率報告等的路徑
<!-設置Java類被注入字節碼后存放的路徑-->
<property name="bin.instrument.dir" location="../instrbin" />
<!-設置覆蓋率元數據和報告的路徑-->
<property name="coverage.dir" location="../coverage" />
<!--設置junit報告的路徑 -->
<property name="junitReport.dir" location="../junitReport" />
<!-設置主題代碼bin路徑-->
<property name="bin.main.dir" location="../srcbin" />
<!-設置測試代碼bin路徑-->
<property name="bin.test.dir" location="../testbin" />
<!--設置主題代碼源路徑-->
<property name="src.main.dir" location="../../SampleProject/src" />
<!--設置測試代碼源路徑-->
<property name="src.test.dir" location="../../SampleProjectTest/test"
/>
<!-指示需要注入字節碼的Java類的路徑-->
<path id="classpath.main">
<pathelement location="${bin.main.dir}" />
</path>
<!-指示 emma.jar 和emma_ant.jar 的路徑-->
<path id="emma.lib">
<pathelement location="${libs}/emma.jar" />
<pathelement location="${libs}/emma_ant.jar" />
</path>
<!-允許emma-->
<property name="emma.enabled" value="true" />
|
其中目錄${ bin.instrument.dir }存放被注入字節碼的類,"emma.lib" 指向 emma 資源所在的位置。
清單2為 ANT 定義 EMMA 任務
<!-為ANT添加EMMA任務-->
<taskdef resource="emma_ant.properties" classpathref="emma.lib" />
|
清單3編譯源代碼和測試代碼
<target name="compile-src.main">
<mkdir dir="${bin.main.dir}" />
<javac destdir="${bin.main.dir}" debug="on">
<src path="${src.main.dir}" />
</javac>
<copy todir="${bin.main.dir}">
<fileset dir="${src.main.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="compile-src.test">
<mkdir dir="${bin.test.dir}" />
<javac destdir="${bin.test.dir}" debug="on">
<src path="${src.test.dir}" />
<classpath location="${bin.main.dir}" />
</javac>
<copy todir="${bin.test.dir}">
<fileset dir="${src.test.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
|
編譯分兩階段,先編譯源代碼,然后再編譯測試用例代碼。
清單4在所要測試類的代碼中插入字節碼
<!-對編譯在路徑bin.main.dir中的Java類注入字節碼,
并且把注入字節碼的新Java類存放到路徑bin.instrument.dir-->
<!-覆蓋率的元數據存放在路徑coverage.dir中-->
<target name="instrument">
<mkdir dir="${bin.instrument.dir}" />
<mkdir dir="${coverage.dir}" />
<emma enabled="${emma.enabled}">
<instr instrpathref="classpath.main"
destdir="${bin.instrument.dir}"
metadatafile="${coverage.dir}/metadata.emma"
merge="true">
</instr>
</emma>
<copy todir="${bin.instrument.dir}">
<fileset dir="${bin.main.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
|
當${emma.enabled}為 true 時,才生成插入字節碼的類。<instr>中指定了要 instrument 的類的地址, instrumented 后類存放的地址,以及 metadata 存放的地址。
清單5運行測試用例,得到一些生成報告的元數據
<!-執行測試用例同時生成junit測試報告和emma代碼覆蓋率報告-->
<target name="test">
<mkdir dir="${junitReport.dir}" />
<junit fork="true" forkmode="once"
printsummary="withOutAndErr"
errorproperty="test.error"
showoutput="on">
<!-指明代碼覆蓋率的元數據的存放位置-->
<jvmarg
value="-Demma.coverage.out.file=${coverage.dir}/metadata.emma" />
<jvmarg value="-Demma.coverage.out.merge=true" />
<classpath location="${bin.instrument.dir}" />
<classpath location="${bin.test.dir}" />
<classpath refid="emma.lib" />
<formatter type="xml" />
<!-執行所有以Test結尾的junit測試用例-->
<batchtest todir="${junitReport.dir}" haltonfailure="no">
<fileset dir="${bin.test.dir}">
<include name="**/*Test.class" />
</fileset>
</batchtest>
</junit>
</target>
|
在運行測試用例前,需要設置 jvmarg。所有的測試用例都跑在 instrumented 的類上面。
清單6生成 JUnit 報告
<target name="gen-report-junit">
<!-生成junit測試報告-->
<junitreport todir="${junitReport.dir}">
<fileset dir="${junitReport.dir}">
<include name="*" />
</fileset>
<report format="frames" todir="${junitReport.dir}" />
</junitreport>
</target>
|
清單7生成覆蓋率報告
<!-生成代碼覆蓋率報告-->
<target name="gen-report-coverage">
<!-如果屬性emma.enabled的值是true,就生成代碼覆蓋率報告 -->
<emma enabled="${emma.enabled}">
<report sourcepath="${src.main.dir}"
sort="+block,+name,+method,+class"
metrics="method:70,block:80,line:80,class:100">
<fileset dir="${coverage.dir}">
<include name="*.emma" />
</fileset>
<html outfile="${coverage.dir}/coverage.html"
depth="method" columns="name,class,method,block,line" />
</report>
</emma>
</target>
|
<report>中 sourcepath 指明源代碼所在的位置,以便能夠顯示每行代碼的覆蓋情況。Sort指明生成列表的排列順序,"+"表示升序,"-"表示降序。Metrics 可為每個度量指明一個覆蓋率閾值,若未達到該閾值,則該行會被標記出來(前提是報告的形式支持這個功能,如 HTML)。<html>指明以 HTML 形式生成報告,Depth 指明報告的詳細程度,columns 指明生成列表列名的排列順序。


|
回頁首 |
|
顯示報告
我們已經寫好了Ant腳本,接下來你就可以運行該腳本了。這里假設你已經搭好了運行 Ant 和 JUnit 的環境,直接到腳本所在目錄,在命令行敲入 Ant 即可。
下面是各個層次的報告:
圖2整個項目層次的報告
圖3包層次的報告
圖4類層次的報告
圖5用顏色標記的源代碼
你會發現有三種顏色,綠色,紅色和黃色,它們分別表示該行:被測試到,未被測試到,以及部分被測試到。紅色或黃色的部分是需要引起你注意的,bug 也許就隱藏在這部分代碼中,你所需做的就是設計一些測試用例,使它們運行以前未被執行到的語句。如上面那張圖給出了我們一些信息,String 中含有"+"號的情況未被測試到,還有"isPositive"只被測試到 true 或 false 的一種情況,你需要相應的增加一些測試用例。運行新加的測試用例,你也許會發現一些新的 bug,并修正這些 bug。


|
回頁首 |
|
隱藏在報告背后的問題
對于這個簡單的例子,你會發現,我們很容易達到 100% 的測試覆蓋率,你也許會松口氣說:啊,我把所有情況都測試到了,這下放心了。在這里很遺憾的告訴你,EMMA 的功能是有限的,它不支持決策覆蓋和路徑覆蓋。事實上,對于一個稍復雜的工程進行窮盡的測試是不可能的。
清單8決策覆蓋和路徑覆蓋的代碼示例
/**
* Parses the given string to a float number
*
* @param number
* the given string
* @return the float number related with the string
*
* @throws IllegalArgumentException
* if the string is empty, null or can not parse to a float
*/
public float parse(String number) {
if (number.equals("")||number == null ) {
throw new IllegalArgumentException(
"Number string should not be empty or null");
}
StringIterator stringIterator = new StringIterator(number);
getSign(stringIterator);
int integer = getInteger(stringIterator);
float fraction = getFraction(stringIterator);
float total = integer + fraction;
return isPositive ? total : (-1) * total;
}
|
清單9決策覆蓋和路徑覆蓋的測試用例
public void test_parse () {
NumberParser np = new NumberParser();
String number ="";
try {
np.parse(number);
fail("should throw IAE");
} catch (IllegalArgumentException e) {
// pass
}
number = "22.010";
float parsedNumber = np.parse(number);
assertEquals((float) 22.010, parsedNumber);
number = "-22.010";
parsedNumber = np.parse(number);
assertEquals((float) 22.010, parsedNumber);
}
|
運行 Ant 腳本,生成報告,你會發現,測試用例都運行通過了,測試覆蓋報告也表明代碼所有的行都被執行到了。但細心的讀者肯定早已看到上面代碼存在 Bug。若傳進 parse 的 string 為 null 的話,并不是如我們所愿,得到 IllegalArgumentException,而是拋出了 NullPointerException。
雖然下面那行是綠色的,但它只表明每個條件語句都被執行到了,并不能說明每個條件都取到true和false兩種情況。在我們設計的測試用例中,"null == number"只取到 false 一種情況。我們需要在我們的測試用例中加入對 string 情況是 null 的測試。
圖6 決策覆蓋和路徑覆蓋率報告
清單10 修正代碼的 Bug
if (null == number || "".equals(number)) {
|


|
回頁首 |
|
結束語
為你的項目生成覆蓋率報告,EMMA 是個不錯的選擇。通過覆蓋率報告,我們能發現并修復一些隱藏的 bug,我們的軟件會變得更強壯。


|
回頁首 |
|
下載
名字 | 大小 | 下載方法 |
SampleProjects.zip |
419 K |
HTTP |
原文轉自:http://www.anti-gravitydesign.com
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97
|