巧用工具 為Java程序生成代碼做覆蓋統計
51CTO曾經為大家相信介紹過10款常用Java測試工具,代碼覆蓋率工具這項軟件測試過程中使用的一個重要的工具卻從來沒介紹過,來為Java™程序生成測試的覆蓋率結果,并提供了關于怎樣分析結果以改進測試的信息。本文向您展示了怎樣使用IBM一款開發工具(IBM® Rational® Application Developer)中提供的代碼覆蓋工具如何來為Java™程序生成測試的覆蓋率結果。
什么是Rational Code Coverage特性?
代碼覆蓋率是軟件測試的一個重要方面,對于一個構件的總體系統測試來說可能是一個基本的參數。覆蓋工具背后的動機向您(作為開發員或者測試員)提供了關于代碼的一系列觀點,這些代碼在一系列的測試之中會得到檢查。該信息會非常的有用,因為您可以使用它來設計新的測試用例以獲得足夠的覆蓋范圍。
IBM® Rational® Code Coverage特性是一個與IBM® Rational® Application Developer相集成的工具。您可以使用它來生成并分析關于Java程序的覆蓋率統計數據。工具會為測試下的程序生成聲明覆蓋率統計數據(這就是說,執行程序中行的數量與百分比)。
Rational Code Coverage特性現在只能獲得Rational Application Developer 7.5版本及其后續版本。本文假設您使用的是Rational Application Developer 7.5.4版本。對代碼覆蓋率而配置IBM® WebSphere®Application Server的部分假設您使用的是7.0版本,但是提供的指南的一些調整仍然適用于上述版本。
指南
為了適當地分析Rational Code Coverage特性中的覆蓋率統計數據,理解場景背后所用到的技術是非常重要的。
Eclipse Test與Performance Tools Project(TPTP)中提供的Rational Code Coverage特性所使用的工具引擎。Probekit用于控制一個類的比特代碼,并引入覆蓋率數據收集引擎的通用訪問。圖1提供了關于這個過程的一個高層次的概述:
圖1.Rational Code Coverage執行環境的概述
基本快與可執行的單元
Probekit是一種在Eclipse平臺上的框架,并可以操作所謂可執行單元的比特代碼。可執行單元的定義與基本塊的傳統定義有輕微的不同,但是當您在分析結果時,您就需要去關注這點差異了。
根據定義,一個所謂的基本塊就是一系列的指南,這些指南不能再進行分支或者分散。這里的關鍵思想在于,當第一個指南運行的時候,該塊中隨后所有指南都一定會得到執行而且不會得到中斷。接下來的是一個基本塊,它可以認為是一個單個組或者一系列的指南。通常來說,基本塊的結尾是branch,call,throw或者return聲明。
一個可執行的單元由每一個基本快開始,而與每行源代碼相對應的指南與前面版本中的指南有所不同。可執行的單元與基本塊的不同點,在于決定一個可執行單元末尾的因素。例如,pide指南并沒有認為是一個可執行單元的結尾,盡管有例外情況的存在。
Probekit是Rational Code Coverage特性所使用的,以將通用代碼引入到每一個可執行的單元之中。結果來說,您可以定制Rational Code Coverage特性以向組成性(換句話說,就是塊覆蓋率)可執行單元層次報告統計數據。為了知道這些工具是怎樣更改類了,您可以參考接下來的代碼清單1與代碼清單2。代碼清單1提供了未處理類的分解輸出(從javap工具來),同時代碼清單2為處理過的類提供了分解輸出。注意代碼清單2中italics的行就是作為處理步驟一部分導入的代碼部分。
#p#
清單1.未處理的類文件
- Compiled from "Part.java"
- public class com.ibm.storeapp.models.Part extends java.lang.Object{
- public com.ibm.storeapp.models.Part(int);
- Code:
- 0: aload_0
- 1: invokespecial #15; //Method java/lang/Object."<init>":()V
- 4: iload_1
- 5: bipush 10
- 7: if_icmple 18
- 10: aload_0
- 11: iload_1
- 12: invokespecial #18; //Method setDiscountedPrice:(I)V
- 15: goto 23
- 18: aload_0
- 19: iload_1
- 20: putfield #21; //Field price:I
- 23: return
- public int getPrice();
- Code:
- 0: aload_0
- 1: getfield #21; //Field price:I
- 4: ireturn
- }
清單2.每一個可執行單元處理的類文件
- Compiled from "Part.java"
- public class com.ibm.storeapp.models.Part extends java.lang.Object{
- public com.ibm.storeapp.models.Part(int);
- Code:
- 0: ldc #49; //String com/ibm/storeapp/models/Part 2: iconst_0 3: iconst_0 4:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 7: aload_0
- 8: invokespecial #15; //Method java/lang/Object."<init>":()V
- 11: ldc #49; //String com/ibm/storeapp/models/Part 13: iconst_0 14: iconst_1 15:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 18: iload_1
- 19: bipush 10
- 21: if_icmple 46
- 24: ldc #49; //String com/ibm/storeapp/models/Part 26: iconst_0 27: iconst_2 28:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 31: aload_0
- 32: iload_1
- 33: invokespecial #18; //Method setDiscountedPrice:(I)V
- 36: ldc #49; //String com/ibm/storeapp/models/Part 38: iconst_0 39: iconst_3 40:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 43: goto 58
- 46: ldc #49; //String com/ibm/storeapp/models/Part 48: iconst_0 49: iconst_4 50:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 53: aload_0
- 54: iload_1
- 55: putfield #21; //Field price:I
- 58: ldc #49; //String com/ibm/storeapp/models/Part 60: iconst_0 61: iconst_5 62:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 65: return
- public int getPrice();
- Code:
- 0: ldc #49; //String com/ibm/storeapp/models/Part 2: iconst_2 3: iconst_0 4:
- invokestatic #48; //Method llc_probe$Probe_0._executableUnit:(Ljava/lang/String;II)V
- 7: aload_0
- 8: getfield #21; //Field price:I
- 11: ireturn
- static {}; Code: 0: ldc #49; //String com/ibm/storeapp/models/Part 2: ldc #55;
- //String Part.java 4: ldc #57; //String <init>(I)V+setDiscountedPrice(I)V
- +getPrice()I 6: ldc #59; //String #10+11032,301,3 8: invokestatic #54;
- //Method llc_probe$Probe_0._staticInitializer:
- (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V 11: return
- }
在Rational Application Developer中生成覆蓋率統計數據
Rational Code Coverage特性的一個主要的優勢在于,您可以通過切換到項目Properties的Code Coverage窗格,來將其在Rational Application Developer中的Java項目上激活,如圖2所示。
圖2.項目Properties中的代碼覆蓋窗格
#p#
選擇圖2中的Enable code coverage復選框以激活項目的代碼覆蓋率,并評價覆蓋下項目的類。您還可以使用該窗格來定制可接受的覆蓋率層次。接下來描述了組合的支持層次:
◆類型覆蓋率:一個類中覆蓋的類型百分比
◆方法覆蓋率:一個類中覆蓋的方法百分比
◆行覆蓋率:類文件中覆蓋的行百分比
◆塊覆蓋率:一個類文件中覆蓋的塊的百分比。注意一個塊會參考一個可執行的單元(如以前描述的那樣)
您還可以指定通用的篩選規則,而且它們可以用于控制在項目中評價哪些內容。默認條件下,項目中的所有類都會得到評價,但是您可以創建通用的篩選規則來排除目標包或者指定類型,如果您需要限制結果的話。
Package Explorer
在您激活一個項目中的代碼覆蓋率以后,覆蓋率統計數據就會在下一次程序啟動的時候生成。注意不是所有類型啟動配置都會自動生成統計數據。表1顯示了Rational Application Developer內支持的啟動類型。
表1.支持的啟動配置
啟動類型 |
Java Applet |
OSGi框架 |
JUnit |
JUnit插件測試 |
Java程序 |
Eclipse程序 |
標準Widget Toolkit (SWT)程序 |
該程序是一個不同交通工具(汽車、貨車、摩托車等等)的簡單再現。圖3中是一個概括了該程序結構的UML圖。
圖3.范例程序的UML圖
在項目中有兩種定義好的JUnit測試:TestCar.java與TestCarImproved.java。正如其名字所暗含的一樣,這些測試的目標是Car.java類。而在Rational Application Developer的Java視角中,您可以右擊TestCar.java并選擇Run As > JUnit test來啟動TestCar.java測試。JUnit測試的結果會正常顯示在JUnit視圖中。覆蓋率數據的結果會集成到Rational Application Developer UI中,而且您可以切換回Package Explorer來分析它們。圖4顯示了TestCar.java測試的一個范例結果。
圖4.Package Explorer中顯示的TestCar.java的覆蓋率數據
默認條件下,UI只與行覆蓋率信息一起注釋;但是,您可以在工作臺偏好中更改它們,并且可以選擇為包、類型以及塊而包含覆蓋率。每一個Java項目的百分比是最后一次執行代碼覆蓋率的中斷。您可以在Package Explorer中深入研究各種Java工件(例如,類、類型與方法)以得到較低組合層次上的覆蓋率統計數據。
結果得到的結果的顏色情況取決于成功率:默認條件下,紅色意味著沒有達到可接受的覆蓋率層次,而綠色則意味著得到了適當的覆蓋率范圍。一般來說,測試的目的在于達到類可接受覆蓋率層次的結果。
基于如圖4所示的結果,第一個測試是不充分的:Car類(以及抽象父類AbstractFourWheelVehicle和Vehicle)并不能達到適當的覆蓋率層次。幸運的是,您有第二次嘗試的機會:TestCarImproved.java。您可以再一次將測試作為一次正常的JUnit執行,而結果將會在Package Explorer中進行自動更新(圖5)。
圖5.Package Explorer中顯示的TestCarImproved.java的代碼覆蓋率數據
Java編輯器
行覆蓋率結果也是顯示的,并在Java編輯器中有所標記,而您可以使用它來得到一個更加明確的指示,也就是每一類中涉及到了哪一行。在生成覆蓋率數據之后,您就可以使用Java編輯器來在項目中打開任意的類了,編輯器中左邊的標尺欄顯示了關于覆蓋率的信息。圖6顯示了Vehicle.java的結果:
圖6.Java編輯器中顯示的覆蓋率結果
顏色編輯與在Package Explorer中所顯示的是一樣的。也就是,默認條件下,綠色的行是覆蓋的而紅色的則不是覆蓋的。在Java編輯器中查看結果有一個微弱的優勢,那就是它還指示了部分覆蓋的行。當在源代碼中有不止一個的可執行單元時就會產生部分覆蓋的行,但是它們中只有一個可以被執行。例如,查看圖6中setTargetSpeed(int speed)方法中第一行的代碼:第一個可執行的單元是if聲明,而第二個可執行的單元則是return聲明。默認條件下,一個部分的行會被標上黃色。
#p#
生成報告
您可以將代碼覆蓋率結果數據匯編到報告之中并在Rational Application Developer中查看它們,或者將它們保存到文件系統中以便未來的分析。您可以生成兩種不同類型的報告:Workbench報告(基于Eclipse)與HTML報告。為了生成一份報告,您可以選擇Run>Code Coverage>Generate Report。圖7顯示了報告生成對話框。
圖7.報告生成對話框
您可以在Rational Application Developer中使用對話框中的Quick View選項來創建并查看一個報告,或者使用Save Report選項將其保存到文件系統中去。
工作臺報告
工作臺報告(也叫做基于Eclipse的報告)為項目提供了所有覆蓋率統計數據的穩固視圖,并包含了執行時項目中所有類的覆蓋率數據。圖8顯示了一個基于Eclipse流傳的報告。
圖8.一個基于Eclipse報告的覆蓋率結果
工作臺報告與Rational Application Developer相集成具有額外的優勢,因為您可以使用它們作為一個快速的工具,以提供了關于部分代碼的視角,這些代碼需要改進的測試覆蓋率數據。如圖8所示,工作臺報告中的統計數據包含了所有層次組成的覆蓋率信息:從一個包到一種方法。右擊任意的Java工件會顯示出一個帶有兩種操作的彈出菜單:在Package Explorer中顯示與在Java編輯器中將其打開。對于識別和研究帶有低覆蓋率的代碼區域來說,它們是非常有用的工具,因為通過將它們在適當的瀏覽器或者編輯器中打開,從而強調了代碼的選擇區域。
#p#
HTML報告
HTML報告顯示了基于Eclipse報告所提供的相同類型的信息,但是呈現的格式卻是HTML的。這些報告能夠發揮一定程度的作用,因為它們為在獨立于Rational Application Developer之外去分析覆蓋率數據提供了一種有效的方法,您可以與團隊的其他成員一起分享,或者將其發布到一個網站上以方便查看。
在工作臺的外部生成統計數據
Rational Code Coverage工具的一個主要特性是其在Rational Application Developer外部生成統計數據的能力。它提供了額外的靈活性,并使得您可以定制環境以利用系統中的Rational Code Coverage特性。例如,一個自然的合并過程就是創建一個構建環境并使用JUnit測試來生成統計數據。
通過執行以下的三個步驟:評價,執行以及生成報告,您可以將Rational Code Coverage特性集成到您的環境之中。
第1步.評價
您可以使用兩種不同的方法來評價您的程序。第一個就是使用<RAD_HOME>/plugins/com.ibm.rational.llc.engine_<date>/scripts目錄中提供的instrument.bat/sh腳本。本文并沒有關注這個腳本,但是您可以參考Rational Application Developer文獻以得到更多的信息,如果需要的話。第二個方法是使用Rational Code Coverage特性提供的評價Ant任務。代碼清單3顯示了評價任務配置的范例用法,以得到本文中的范例程序。
清單3.本文范例程序的評價Ant任務的范例用法
- <target name="instrument">
- <taskdef name="instrument"
- classname="com.ibm.rational.llc.engine.instrumentation.anttask.InstrumentationTask"
- classpath="{path to com.ibm.rational.llc.engine plugin}"/>
- <instrument saveBackups="true"
- baseLineFile="project.baseline"
- buildPath="VehicleProject"
- outputDir="VehicleProjectInstr"/>
- </target>
對預期參數的快速預覽,已經列在后續的表2中。
表2.指南任務的輸入參數
參數描述
buildPath對文件系統上項目的路徑
outputDir(可選的)評價項目的輸出目錄。如果沒有指定,buildPath中的類將會進行評價。
baseLineFile(可選的)基線項目索引文件的輸出位置。查看接下來的段落以得到關于該文件更多的信息。
saveBackups(可選的)如果在評價之前先備份原始的類文件,那么您可以設置為true。
評價的兩種方法都會輸出一個基線文件。所謂的基線文件是一個特定于Rational Code Coverage特性的概念。基線文件包含了項目中所有類的一個索引,并維護了關于每一個類的額外元數據。該文件在報告階段(接下來的第3步)使用以決定程序中的哪一個類不被覆蓋。該步是需要的,因為Rational Code Coverage數據收集引擎只是在Java™ Virtual Machine(JVM)載入類時才會標記一個類,所以沒有執行的類的列表在沒有元數據存在的條件下就不能進行決定了。如果基線文件沒有在報告時出現,那么沒有載入的類將不會出現在報告中。
第2步.執行
為了執行評價好的類,您必須在啟動時對Java環境做適當的配置。執行過程中所需的兩個特定的參數解釋如下:
◆-Dcoverage.out.file=<absolute path to output file>:該JUM論斷指定的文件就是覆蓋率統計數據的輸出位置
◆向classpath添加<Rational Application Developer HOME>/plugins/com.ibm.rational.llc.engine_<date>/RLC.jar:因為代碼已經進行了評價并得到了Rational Code Coverage數據搜索引擎的回饋,RLC.jar文件需要在運行時位于classpath處。
JUnit Ant任務提供了這些參數。代碼清單4提供了范例用法。
清單4.怎樣指定Ant啟動中Rational Code Coverage特性論斷的范例
- <target name="run">
- <junit showoutput="true" fork="yes">
- <jvmarg value="-Dcoverage.out.file={absolute path to the output file}"/>
- <classpath>
- <pathelement location="{absolute path to the
- <Rational Application Developer HOME>/plugins/com.ibm.rational.llc.engine_<date>
- /RLC.jar file}"/>
- <pathelement location="{path to the project classes}"/>
- <pathelement path="{absolute path to the junit.jar}" />
- </classpath>
- <test name="com.ibm.vehicles.tests.TestCar" outfile="TestCar" />
- </junit>
- </target>
#p#
第3步.生成報告
您可以使用Rational Code Coverage特性所提供的另外一項Ant任務來生成報告。該項任務使用BIRT Eclipse.org項目所提供的報告功能,并因此需要您去下載BIRT V2.3.2 Reporting Engine獨立版。該操作可以通過訪問http://www.eclipse.org/birt/download,選擇V2.3.2版本并下載Report Engine版本來完成。注意該Ant任務只能產生HTML報告。
清單5提供了報告Ant任務的范例用法。注意,作為輸入,它需要在第2步中所生成的覆蓋率數據以及在第1步中(可選)所生成的基線文件。
清單5.本文中范例程序報告生成Ant任務的范例
- <target name="generate-report">
- <path id="lib.path">
- <pathelement location="{absolute path to the
- <Rational Application Developer HOME>/plugins/
- com.ibm.rational.llc.common_<date>.jar plugin}"/>
- <pathelement location="{absolute path to the
- <Rational Application Developer HOME>/plugins/
- com.ibm.rational.llc.report_<date> plugin}"/>
- <pathelement location="{absolute path to the
- <Rational Application Developer HOME>/plugins/
- org.eclipse.equinox.common_<date>.jar plugin}"/>
- <fileset dir="{absolute path to the BIRT ReportEngine directory}\lib" includes="*.jar"/>
- </path>
- <taskdef name="code-coverage-report"
- classname="com.ibm.rational.llc.report.birt.adapters.ant.ReportGenerationTask"
- classpathref="lib.path"/>
- <code-coverage-report
- outputDir="{absolute path to the report output directory}"
- coverageDataFile="{absolute path to the coveragedata file generated in step 1}"
- baseLineFiles="{absolute path to the baseline file generated in step 1}"/>
- </target>
在圖9中顯示有一個范例HTML報告。使用Ant任務生成HTML報告會提供一種方法,用戶可以通過這種方法來查看獨立于Rational Application Developer之外Ant環境中生成的統計數據。
圖9.HTML報告中的覆蓋率結果
Ant環境提供了范例腳本以及構建文件,該環境可以用于指導、執行并生成關于范例程序的報告。如果您對測試該環境感興趣,那么您可以參考一下Standalone.zip文件中的README文件。
#p#
在WebSphere Application Server上生成統計數據
使用WebSphere Application Server來生成代碼覆蓋率統計數據在這里是支持的,但是不幸的是,這個版本中并不支持來自Rational Application Developer內部的自動化配置。但是,版本中提供的Rational Code Coverage特性足夠靈活以集成到服務器環境中去,包括WebSphere Application Server。為了對代碼覆蓋率而配置您的WebSphere Application Server,您需要按照以下步驟進行操作:
1.啟動服務器
2.登錄到Administrative Console
3.在左邊的窗格中,展開Servers
4.展開Server Types
5.點擊WebSphere程序服務器
6.選擇適當的程序服務器
7.展開右部窗格選項區域內Server Infrastructure部分中Java and Process Management項
8.點擊Process definition
9.點擊Additional Properties部分中的Java Virtual Machine
10.在 Boot Classpath部分中,添加RLC.jar文件。如上面介紹的那樣,該.jar文件位于Rational Code Coverage數據收集引擎中,并位于<Rational Application Developer HOME>/plugins/com.ibm.rational.llc.engine_<date>/RLC.jar
11.在Generic JVM arguments中,添加-Dcoverage.out.file={output file} JVM論斷。如上面所述的那樣,該論斷指定了應該將輸出的統計數據保存在什么地方。
12.保存服務器配置并重啟服務器。
圖10顯示了在作出以上所做的變更之后Administrative Console的屏幕截圖。注意在每一個服務器實例的后面必須有一個指南,這些服務器實例會執行一個代碼覆蓋率程序。
圖10.WebSphere Application Server對Rational Code Coverage特性的配置
在服務器對代碼覆蓋率進行配置之后,您就可以手動對服務器進行代碼覆蓋率的配置了(從Administration Console上進行),或者使用Rational Application Developer中的集成支持功能。注意覆蓋率的結果不會自動導入到Rational Application Developer中以進行分析,這樣您需要執行接下來的步驟來將統計數據導回到工作區中:
1.在Rational Application Developer中的Java視角中,右擊Package Explorer并選擇Import
2.展開Code Coverage
3.選擇Code Coverage Data File并點擊Next
4.選擇Data is located on the file system選項并點擊Next
5.在Coverage Data file區域中,選擇服務器所提供的文件系統上的覆蓋率數據
6.在Into folder區域中,選擇工作區中的一個目錄以保存導入的文件。
7.在Associate with Project區域中選擇適當的項目。您應該將統計數據與工作區中的項目聯系起來,工作區中包含的源代碼用于在服務器上生成統計數據。
8.點擊Finish
當覆蓋率數據文件位于工作區中時,您可以在UI中顯示統計數據并生成報告。您可以右擊覆蓋率文件并選擇Code Coverage > Show code coverage indicators或者Generate Report來完成該操作。該功能可以使您更加受益,因為它提供了對分析Rational Application Developer中結果所用所有工具的訪問途徑。
Paul Klicnik 是位于安大略省馬克姆的 IBM 多倫多實驗室的一名軟件開發人員。他自從 2008 年以來從事代碼覆蓋工作,從 2006 年從事性能和測試工具領域的工作。Paul 從事過多個 IBM 核心產品的工作,包括 IBM Rational Performance Tester 和 IBM Rational Application Developer,還有 Eclipse 測試和性能工具項目(TPTP)項目。
【編輯推薦】