GHawk

          用 Cobertura 測(cè)量測(cè)試覆蓋率

          http://www-128.ibm.com/developerworks/cn/java/j-cobertura/

          用 Cobertura 測(cè)量測(cè)試覆蓋率

          找出隱藏 bug 的未測(cè)試到的代碼

          developerWorks
          文檔選項(xiàng)
          將此頁作為電子郵件發(fā)送

          將此頁作為電子郵件發(fā)送

          未顯示需要 JavaScript 的文檔選項(xiàng)


          對(duì)此頁的評(píng)價(jià)

          幫助我們改進(jìn)這些內(nèi)容


          級(jí)別: 初級(jí)

          Elliotte Rusty Harold, 副教授, Polytechnic University

          2005 年 5 月 26 日

          Cobertura 是一種開源工具,它通過檢測(cè)基本的代碼,并觀察在測(cè)試包運(yùn)行時(shí)執(zhí)行了哪些代碼和沒有執(zhí)行哪些代碼,來測(cè)量測(cè)試覆蓋率。除了找出未測(cè)試到的代碼并發(fā)現(xiàn) bug 外,Cobertura 還可以通過標(biāo)記無用的、執(zhí)行不到的代碼來優(yōu)化代碼,還可以提供 API 實(shí)際操作的內(nèi)部信息。Elliotte Rusty Harold 將與您分享如何利用代碼覆蓋率的最佳實(shí)踐來使用 Cobertura。

          盡管測(cè)試先行編程(test-first programming)和單元測(cè)試已不能算是新概念,但測(cè)試驅(qū)動(dòng)的開發(fā)仍然是過去 10 年中最重要的編程創(chuàng)新。最好的一些編程人員在過去半個(gè)世紀(jì)中一直在使用這些技術(shù),不過,只是在最近幾年,這些技術(shù)才被廣泛地視為在時(shí)間及成本預(yù)算內(nèi)開發(fā)健壯的無缺陷軟件的關(guān)鍵所在。但是,測(cè)試驅(qū)動(dòng)的開發(fā)不能超過測(cè)試所能達(dá)到的程度。測(cè)試改進(jìn)了代碼質(zhì)量,但這也只是針對(duì)實(shí)際測(cè)試到的那部分代碼而言的。您需要有一個(gè)工具告訴您程序的哪些部分沒有測(cè)試到,這樣就可以針對(duì)這些部分編寫測(cè)試代碼并找出更多 bug。

          Mark Doliner 的 Cobertura (cobertura 在西班牙語是覆蓋的意思)是完成這項(xiàng)任務(wù)的一個(gè)免費(fèi) GPL 工具。Cobertura 通過用額外的語句記錄在執(zhí)行測(cè)試包時(shí),哪些行被測(cè)試到、哪些行沒有被測(cè)試到,通過這種方式來度量字節(jié)碼,以便對(duì)測(cè)試進(jìn)行監(jiān)視。然后它生成一個(gè) HTML 或者 XML 格式的報(bào)告,指出代碼中的哪些包、哪些類、哪些方法和哪些行沒有測(cè)試到??梢葬槍?duì)這些特定的區(qū)域編寫更多的測(cè)試代碼,以發(fā)現(xiàn)所有隱藏的 bug。

          閱讀 Cobertura 輸出

          我們首先查看生成的 Cobertura 輸出。圖 1 顯示了對(duì) Jaxen 測(cè)試包運(yùn)行 Cobertura 生成的報(bào)告(請(qǐng)參閱 參考資料)。從該報(bào)告中,可以看到從很好(在 org.jaxen.expr.iter 包中幾乎是 100%)到極差(在 org.jaxen.dom.html 中完全沒有覆蓋)的覆蓋率結(jié)果。


          圖 1. Jaxen 的包級(jí)別覆蓋率統(tǒng)計(jì)數(shù)據(jù)

          Cobertura 通過被測(cè)試的行數(shù)和被測(cè)試的分支數(shù)來計(jì)算覆蓋率。第一次測(cè)試時(shí),兩種測(cè)試方法之間的差別并不是很重要。Cobertura 還為類計(jì)算平均 McCabe 復(fù)雜度(請(qǐng)參閱 參考資料)。

          可以深入挖掘 HTML 報(bào)告,了解特定包或者類的覆蓋率。圖 2 顯示了 org.jaxen.function 包的覆蓋率統(tǒng)計(jì)。在這個(gè)包中,覆蓋率的范圍從 SumFunction 類的 100% 到 IdFunction 類的僅為 5%。


          圖 2. org.jaxen.function 包中的代碼覆蓋率

          進(jìn)一步深入到單獨(dú)的類中,具體查看哪一行代碼沒有測(cè)試到。圖 3 顯示了 NameFunction 類中的部分覆蓋率。最左邊一欄顯示行號(hào)。后一欄顯示了執(zhí)行測(cè)試時(shí)這一行被執(zhí)行的次數(shù)??梢钥闯觯?112 行被執(zhí)行了 100 次,第 114 行被執(zhí)行了 28 次。用紅色突出顯示的那些行則根本沒有測(cè)試到。這個(gè)報(bào)告表明,雖然從總體上說該方法被測(cè)試到了,但實(shí)際上還有許多分支沒有測(cè)試到。


          圖 3. NameFunction 類中的代碼覆蓋率

          Cobertura 是 jcoverage 的分支(請(qǐng)參閱 參考資料)。GPL 版本的 jcoverage 已經(jīng)有一年沒有更新過了,并且有一些長(zhǎng)期存在的 bug,Cobertura 修復(fù)了這些 bug。原來的那些 jcoverage 開發(fā)人員不再繼續(xù)開發(fā)開放源碼,他們轉(zhuǎn)向開發(fā) jcoverage 的商業(yè)版和 jcoverage+,jcoverage+ 是一個(gè)從同一代碼基礎(chǔ)中發(fā)展出來的封閉源代碼產(chǎn)品。開放源碼的奇妙之處在于:一個(gè)產(chǎn)品不會(huì)因?yàn)樵_發(fā)人員決定讓他們的工作獲得相應(yīng)的報(bào)酬而消亡。

          確認(rèn)遺漏的測(cè)試

          利用 Cobertura 報(bào)告,可以找出代碼中未測(cè)試的部分并針對(duì)它們編寫測(cè)試。例如,圖 3 顯示 Jaxen 需要進(jìn)行一些測(cè)試,運(yùn)用 name() 函數(shù)對(duì)文字節(jié)點(diǎn)、注釋節(jié)點(diǎn)、處理指令節(jié)點(diǎn)、屬性節(jié)點(diǎn)和名稱空間節(jié)點(diǎn)進(jìn)行測(cè)試。

          如果有許多未覆蓋的代碼,像 Cobertura 在這里報(bào)告的那樣,那么添加所有缺少的測(cè)試將會(huì)非常耗時(shí),但也是值得的。不一定要一次完成它。您可以從被測(cè)試的最少的代碼開始,比如那些所有沒有覆蓋的包。在測(cè)試所有的包之后,就可以對(duì)每一個(gè)顯示為沒有覆蓋的類編寫一些測(cè)試代碼。對(duì)所有類進(jìn)行專門測(cè)試后,還要為所有未覆蓋的方法編寫測(cè)試代碼。在測(cè)試所有方法之后,就可以開始分析對(duì)未測(cè)試的語句進(jìn)行測(cè)試的必要性。

          (幾乎)不留下任何未測(cè)試的代碼

          是否有一些可以測(cè)試但不應(yīng)測(cè)試的內(nèi)容?這取決于您問的是誰。在 JUnit FAQ 中,J. B. Rainsberger 寫到“一般的看法是:如果 自身 不會(huì)出問題,那么它會(huì)因?yàn)樘?jiǎn)單而不會(huì)出問題。第一個(gè)例子是 getX() 方法。假定 getX() 方法只提供某一實(shí)例變量的值。在這種情況下,除非編譯器或者解釋器出了問題,否則 getX() 是不會(huì)出問題的。因此,不用測(cè)試 getX(),測(cè)試它不會(huì)帶來任何好處。對(duì)于 setX() 方法來說也是如此,不過,如果 setX() 方法確實(shí)要進(jìn)行任何參數(shù)驗(yàn)證,或者說確實(shí)有副作用,那么還是有必要對(duì)其進(jìn)行測(cè)試。”

          理論上,對(duì)未覆蓋的代碼編寫測(cè)試代碼不一定就會(huì)發(fā)現(xiàn) bug。但在實(shí)踐中,我從來沒有碰到?jīng)]有發(fā)現(xiàn) bug 的情況。未測(cè)試的代碼充滿了 bug。所做的測(cè)試越少,在代碼中隱藏的、未發(fā)現(xiàn)的 bug 就會(huì)越多。

          我不同意。我已經(jīng)記不清在“簡(jiǎn)單得不會(huì)出問題”的代碼中發(fā)現(xiàn)的 bug 的數(shù)量了。確實(shí),一些 getter 和 setter 很簡(jiǎn)單,不可能出問題。但是我從來就沒有辦法區(qū)分哪些方法是真的簡(jiǎn)單得不會(huì)出錯(cuò),哪些方法只是看上去如此。編寫覆蓋像 setter 和 getter 這樣簡(jiǎn)單方法的測(cè)試代碼并不難。為此所花的少量時(shí)間會(huì)因?yàn)樵谶@些方法中發(fā)現(xiàn)未曾預(yù)料到的 bug 而得到補(bǔ)償。

          一般來說,開始測(cè)量后,達(dá)到 90% 的測(cè)試覆蓋率是很容易的。將覆蓋率提高到 95% 或者更高就需要?jiǎng)右幌履X筋。例如,可能需要裝載不同版本的支持庫,以測(cè)試沒有在所有版本的庫中出現(xiàn)的 bug。或者需要重新構(gòu)建代碼,以便測(cè)試通常執(zhí)行不到的部分代碼??梢詫?duì)類進(jìn)行擴(kuò)展,讓它們的受保護(hù)方法變?yōu)楣卜椒?,這樣就可以對(duì)這些方法進(jìn)行測(cè)試。這些技巧看起來像是多此一舉,但是它們?cè)鴰椭以谝话氲臅r(shí)間內(nèi)發(fā)現(xiàn)更多的未發(fā)現(xiàn)的 bug。

          并不總是可以得到完美的、100% 的代碼覆蓋率。有時(shí)您會(huì)發(fā)現(xiàn),不管對(duì)代碼如何改造,仍然有一些行、方法、甚至是整個(gè)類是測(cè)試不到的。下面是您可能會(huì)遇到的挑戰(zhàn)的一些例子:

          • 只在特定平臺(tái)上執(zhí)行的代碼。例如,在一個(gè)設(shè)計(jì)良好的 GUI 應(yīng)用程序中,添加一個(gè) Exit 菜單項(xiàng)的代碼可以在 Windows PC 上運(yùn)行,但它不能在 Mac 機(jī)上運(yùn)行。

          • 捕獲不會(huì)發(fā)生的異常的 catch 語句,比如在從 ByteArrayInputStream 進(jìn)行讀取操作時(shí)拋出的 IOException。

          • 非公共類中的一些方法,它們永遠(yuǎn)也不會(huì)被實(shí)際調(diào)用,只是為了滿足某個(gè)接口契約而必須實(shí)現(xiàn)。

          • 處理虛擬機(jī) bug 的代碼塊,比如說,不能識(shí)別 UTF-8 編碼。

          考慮到上面這些以及類似的情況,我認(rèn)為一些極限程序員自動(dòng)刪除所有未測(cè)試代碼的做法是不切實(shí)際的,并且可能具有一定的諷刺性。不能總是獲得絕對(duì)完美的測(cè)試覆蓋率并不意味著就不會(huì)有更好的覆蓋率。

          然而,比執(zhí)行不到的語句和方法更常見的是殘留代碼,它不再有任何作用,并且從代碼基中去掉這些代碼也不會(huì)產(chǎn)生任何影響。有時(shí)可以通過使用反射來訪問私有成員這樣的怪招來測(cè)試未測(cè)試的代碼。還可以為未測(cè)試的、包保護(hù)(package-protected)的代碼來編寫測(cè)試代碼,將測(cè)試類放到將要測(cè)試的類所在那個(gè)包中。但最好不要這樣做。所有不能通過發(fā)布的(公共的和受保護(hù)的)接口訪問的代碼都應(yīng)刪除。執(zhí)行不到的代碼不應(yīng)當(dāng)成為代碼基的一部分。代碼基越小,它就越容易被理解和維護(hù)。

          不要漏掉測(cè)量單元測(cè)試包和類本身。我不止一次注意到,某些個(gè)測(cè)試方法或者類沒有被測(cè)試包真正運(yùn)行。通常這表明名稱規(guī)范中存在問題(比如將一個(gè)方法命名為 tesSomeReallyComplexCondition,而不是將其命名為 testSomeReallyComplexCondition),或者忘記將一個(gè)類添加到主 suite() 方法中。在其他情況下,未預(yù)期的條件導(dǎo)致跳過了測(cè)試方法中的代碼。不管是什么情況,都是雖然已經(jīng)編寫了測(cè)試代碼,但沒有真正運(yùn)行它。JUnit 不會(huì)告訴您它沒有像您所想的那樣運(yùn)行所有測(cè)試,但是 Cobertura 會(huì)告訴您。找出了未運(yùn)行的測(cè)試后,改正它一般很容易。



          回頁首


          運(yùn)行 Cobertura

          在了解了測(cè)量代碼覆蓋率的好處后,讓我們?cè)賮碛懻撘幌氯绾斡?Cobertura 測(cè)量代碼覆蓋率的具體細(xì)節(jié)。Cobertura 被設(shè)計(jì)成為在 Ant 中運(yùn)行?,F(xiàn)在還沒有這方面的 IDE 插件可用,不過一兩年內(nèi)也許就會(huì)有了。

          首先需要在 build.xml 文件中添加一個(gè)任務(wù)定義。以下這個(gè)頂級(jí) taskdef 元素將 cobertura.jar 文件限定在當(dāng)前工作目錄中:

          <taskdef classpath="cobertura.jar" resource="tasks.properties" />

          然后,需要一個(gè) cobertura-instrument 任務(wù),該任務(wù)將在已經(jīng)編譯好的類文件中添加日志代碼。todir 屬性指定將測(cè)量類放到什么地方。fileset 子元素指定測(cè)量哪些 .class 文件:

          <target name="instrument">
            <cobertura-instrument todir="target/instrumented-classes">
              <fileset dir="target/classes">
                <include name="**/*.class"/>
              </fileset>
            </cobertura-instrument>
          </target>

          用通常運(yùn)行測(cè)試包的同一種類型的 Ant 任務(wù)運(yùn)行測(cè)試。惟一的區(qū)別在于:被測(cè)量的類必須在原始類出現(xiàn)在類路徑中之前出現(xiàn)在類路徑中,而且需要將 Cobertura JAR 文件添加到類路徑中:

          <target name="cover-test" depends="instrument">
            <mkdir dir="${testreportdir}" />
            <junit dir="./" failureproperty="test.failure" printSummary="yes" 
                   fork="true" haltonerror="true">
              <!-- Normally you can create this task by copying your existing JUnit
                   target, changing its name, and adding these next two lines.
                   You may need to change the locations to point to wherever 
                   you've put the cobertura.jar file and the instrumented classes. -->
              <classpath location="cobertura.jar"/>
              <classpath location="target/instrumented-classes"/>
              <classpath>
                <fileset dir="${libdir}">
                  <include name="*.jar" />
                </fileset>
                <pathelement path="${testclassesdir}" />
                <pathelement path="${classesdir}" />
              </classpath>
              <batchtest todir="${testreportdir}">
                <fileset dir="src/java/test">
                  <include name="**/*Test.java" />
                  <include name="org/jaxen/javabean/*Test.java" />
                </fileset>
              </batchtest>
            </junit>
          </target>>

          Jaxen 項(xiàng)目使用 JUnit 作為其測(cè)試框架,但是 Cobertura 是不受框架影響的。它在 TestNG、Artima SuiteRunner、HTTPUni 或者在您自己在地下室開發(fā)的系統(tǒng)中一樣工作得很好。

          最后,cobertura-report 任務(wù)生成本文開始部分看到的那個(gè) HTML 文件:

          <target name="coverage-report" depends="cover-test">
           <cobertura-report srcdir="src/java/main" destdir="cobertura"/>
          </target>

          srcdir 屬性指定原始的 .java 源代碼在什么地方。destdir 屬性指定 Cobertura 放置輸出 HTML 的那個(gè)目錄的名稱。

          在自己的 Ant 編譯文件中加入了類似的任務(wù)后,就可以通過鍵入以下命令來生成一個(gè)覆蓋報(bào)告:

          % ant instrument
          % ant cover-test
          % ant coverage-report

          當(dāng)然,如果您愿意的話,還可以改變目標(biāo)任務(wù)的名稱,或者將這三項(xiàng)任務(wù)合并為一個(gè)目標(biāo)任務(wù)。



          回頁首


          結(jié)束語

          Cobertura 是敏捷程序員工具箱中新增的一個(gè)重要工具。通過生成代碼覆蓋率的具體數(shù)值,Cobertura 將單元測(cè)試從一種藝術(shù)轉(zhuǎn)變?yōu)橐婚T科學(xué)。它可以尋找測(cè)試覆蓋中的空隙,直接找到 bug。測(cè)量代碼覆蓋率使您可以獲得尋找并修復(fù) bug 所需的信息,從而開發(fā)出對(duì)每個(gè)人來說都更健壯的軟件。



          回頁首


          參考資料



          回頁首


          關(guān)于作者

          Elliotte Rusty Harold 出生在新奧爾良,現(xiàn)在他還定期回老家喝一碗美味的秋葵湯。不過目前,他和妻子 Beth 定居在紐約臨近布魯克林的 Prospect Heights,同住的還有他的貓咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大學(xué)計(jì)算機(jī)科學(xué)的副教授,講授 Java 技術(shù)和面向?qū)ο缶幊?。他?Cafe au Lait 網(wǎng)站是 Internet 上最受歡迎的獨(dú)立 Java 站點(diǎn)之一,姊妹站點(diǎn) Cafe con Leche 已經(jīng)成為最受歡迎的 XML 站點(diǎn)之一。他的著作包括 Effective XML Processing XML with Java 、 Java Network Programming The XML 1.1 Bible 。目前他正在從事 XML 的 XOM API、Jaxen XPath 引擎和 Jester 測(cè)試覆蓋率工具的開發(fā)工作。


          posted on 2005-12-17 11:23 GHawk 閱讀(428) 評(píng)論(0)  編輯  收藏 所屬分類: 軟件測(cè)試


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 汉川市| 荆州市| 舒兰市| 平湖市| 阳信县| 鱼台县| 天全县| 夏河县| 伊川县| 尚志市| 凌海市| 全南县| 东台市| 五华县| 内黄县| 定远县| 娱乐| 黄大仙区| 中超| 富平县| 石台县| 克拉玛依市| 清河县| 亳州市| 莎车县| 平远县| 临武县| 岳西县| 巴南区| 佛教| 永康市| 盐边县| 台南县| 五常市| 泰宁县| 康马县| 和田市| 达日县| 九寨沟县| 丽水市| 贞丰县|