圖 3. NameFunction 類中的代碼覆蓋率
Cobertura 是 jcoverage 的分支(請參閱 參考資料)。GPL 版本的 jcoverage 已經(jīng)有一年沒有更新過了,并且有一些長期存在的 bug,Cobertura 修復(fù)了這些 bug。原來的那些 jcoverage 開發(fā)人員不再繼續(xù)開發(fā)開放源碼,他們轉(zhuǎn)向開發(fā) jcoverage 的商業(yè)版和 jcoverage+,jcoverage+ 是一個從同一代碼基礎(chǔ)中發(fā)展出來的封閉源代碼產(chǎn)品。開放源碼的奇妙之處在于:一個產(chǎn)品不會因?yàn)樵_發(fā)人員決定讓他們的工作獲得相應(yīng)的報(bào)酬而消亡。
確認(rèn)遺漏的測試
利用 Cobertura 報(bào)告,可以找出代碼中未測試的部分并針對它們編寫測試。例如,圖 3 顯示 Jaxen 需要進(jìn)行一些測試,運(yùn)用 name() 函數(shù)對文字節(jié)點(diǎn)、注釋節(jié)點(diǎn)、處理指令節(jié)點(diǎn)、屬性節(jié)點(diǎn)和名稱空間節(jié)點(diǎn)進(jìn)行測試。
如果有許多未覆蓋的代碼,像 Cobertura 在這里報(bào)告的那樣,那么添加所有缺少的測試將會非常耗時(shí),但也是值得的。不一定要一次完成它。您可以從被測試的少的代碼開始,比如那些所有沒有覆蓋的包。在測試所有的包之后,可以對每一個顯示為沒有覆蓋的類編寫一些測試代碼。對所有類進(jìn)行專門測試后,還要為所有未覆蓋的方法編寫測試代碼。在測試所有方法之后,可以開始分析對未測試的語句進(jìn)行測試的必要性。
(幾乎)不留下任何未測試的代碼
是否有一些可以測試但不應(yīng)測試的內(nèi)容?這取決于您問的是誰。在 JUnit FAQ 中,J. B. Rainsberger 寫到“一般的看法是:如果 自身 不會出問題,那么它會因?yàn)樘唵味粫鰡栴}。第一個例子是 getX() 方法。假定 getX() 方法只提供某一實(shí)例變量的值。在這種情況下,除非編譯器或者解釋器出了問題,否則 getX() 是不會出問題的。因此,不用測試 getX(),測試它不會帶來任何好處。對于 setX() 方法來說也是如此,不過,如果 setX() 方法確實(shí)要進(jìn)行任何參數(shù)驗(yàn)證,或者說確實(shí)有副作用,那么還是有必要對其進(jìn)行測試。”
理論上,對未覆蓋的代碼編寫測試代碼不一定會發(fā)現(xiàn) bug。但在實(shí)踐中,我從來沒有碰到?jīng)]有發(fā)現(xiàn) bug 的情況。未測試的代碼充滿了 bug。所做的測試越少,在代碼中隱藏的、未發(fā)現(xiàn)的 bug 會越多。
我不同意。我已經(jīng)記不清在“簡單得不會出問題”的代碼中發(fā)現(xiàn)的 bug 的數(shù)量了。確實(shí),一些 getter 和 setter 很簡單,不可能出問題。但是我從來沒有辦法區(qū)分哪些方法是真的簡單得不會出錯,哪些方法只是看上去如此。編寫覆蓋像 setter 和 getter 這樣簡單方法的測試代碼并不難。為此所花的少量時(shí)間會因?yàn)樵谶@些方法中發(fā)現(xiàn)未曾預(yù)料到的 bug 而得到補(bǔ)償。
一般來說,開始測量后,達(dá)到 90% 的測試覆蓋率是很容易的。將覆蓋率提高到 95% 或者更高需要動一下腦筋。例如,可能需要裝載不同版本的支持庫,以測試沒有在所有版本的庫中出現(xiàn)的 bug;蛘咝枰匦聵(gòu)建代碼,以便測試通常執(zhí)行不到的部分代碼?梢詫︻愡M(jìn)行擴(kuò)展,讓它們的受保護(hù)方法變?yōu)楣卜椒ǎ@樣可以對這些方法進(jìn)行測試。這些技巧看起來像是多此一舉,但是它們曾幫助我在一半的時(shí)間內(nèi)發(fā)現(xiàn)更多的未發(fā)現(xiàn)的 bug。
并不總是可以得到完美的、 的代碼覆蓋率。有時(shí)您會發(fā)現(xiàn),不管對代碼如何改造,仍然有一些行、方法、甚至是整個類是測試不到的。下面是您可能會遇到的挑戰(zhàn)的一些例子:
只在特定平臺上執(zhí)行的代碼。例如,在一個設(shè)計(jì)良好的 GUI 應(yīng)用程序中,添加一個 Exit 菜單項(xiàng)的代碼可以在 Windows PC 上運(yùn)行,但它不能在 Mac 機(jī)上運(yùn)行。
捕獲不會發(fā)生的異常的 catch 語句,比如在從 ByteArrayInputStream 進(jìn)行讀取操作時(shí)拋出的 IOException。
非公共類中的一些方法,它們永遠(yuǎn)也不會被實(shí)際調(diào)用,只是為了滿足某個接口契約而必須實(shí)現(xiàn)。
處理虛擬機(jī) bug 的代碼塊,比如說,不能識別 UTF-8 編碼。
考慮到上面這些以及類似的情況,我認(rèn)為一些極限程序員自動刪除所有未測試代碼的做法是不切實(shí)際的,并且可能具有一定的諷刺性。不能總是獲得完美的測試覆蓋率并不意味著不會有更好的覆蓋率。
然而,比執(zhí)行不到的語句和方法更常見的是殘留代碼,它不再有任何作用,并且從代碼基中去掉這些代碼也不會產(chǎn)生任何影響。有時(shí)可以通過使用反射來訪問私有成員這樣的怪招來測試未測試的代碼。還可以為未測試的、包保護(hù)(package-protected)的代碼來編寫測試代碼,將測試類放到將要測試的類所在那個包中。但好不要這樣做。所有不能通過發(fā)布的(公共的和受保護(hù)的)接口訪問的代碼都應(yīng)刪除。執(zhí)行不到的代碼不應(yīng)當(dāng)成為代碼基的一部分。代碼基越小,它越容易被理解和維護(hù)。