測試覆蓋工具對單元測試具有重要的意義,但是經常被誤用。這個月,Andrew Glover 會在他的新系列 —— 追求代碼質量 中向您介紹值得參考的專家意見。第一部分深入地介紹覆蓋報告中數字的真實含義。然后他會提出您可以盡早并經常地利用覆蓋來確保代碼質量的三個方法。
您還記得以前大多數開發人員是如何追求代碼質量的嗎。在那時,有技巧地放置 main()
方法被視為靈活且適當的測試方法。經歷了漫長的道路以后,現在自動測試已經成為高質量代碼開發的基本保證,對此我很感謝。但是這還不是我所要感謝的全部。Java™ 開發人員現在擁有很多通過代碼度量、靜態分析等方法來度量代碼質量的工具。我們甚至已經設法將重構分類成一系列便利的模式!
![]() |
所有的這些新的工具使得確保代碼質量比以前簡單得多,不過您還需要知道如何使用它們。在這個系列中,我將重點闡述有關保證代碼質量的一些有時看上去有點神秘的東西。除了帶您一起熟悉有關代碼質量保證的眾多工具和技術之外,我還將為您說明:
- 定義并有效度量最影響質量的代碼方面。
- 設定質量保證目標并照此規劃您的開發過程。
- 確定哪個代碼質量工具和技術可以滿足您的需要。
- 實現最佳實踐(清除不好的),使確保代碼質量及早并經常地 成為開發實踐中輕松且有效的方面。
在這個月,我將首先看看 Java 開發人員中最流行也是最容易的質量保證工具包:測試覆蓋度量。
這是一個晚上鏖戰后的早晨,大家都站在飲水機邊上。開發人員和管理人員們了解到一些經過良好測試的類可以達到超過 90% 的覆蓋率,正在高興地互換著 NFL 風格的點心。團隊的集體信心空前高漲。從遠處可以聽到 “放任地重構吧” 的聲音,似乎缺陷已成為遙遠的記憶,響應性也已微不足道。但是一個很小的反對聲在說:
女士們,先生們,不要被覆蓋報告所愚弄。
現在,不要誤解我的意思:并不是說使用測試覆蓋工具是愚蠢的。對單元測試范例,它是很重要的。不過更重要的是您如何理解所得到的信息。許多開發團隊會在這兒犯第一個錯。
高覆蓋率只是表示執行了很多的代碼,并不意味著這些代碼被很好地 執行。如果您關注的是代碼的質量,就必須精確地理解測試覆蓋工具能做什么,不能做什么。然后您才能知道如何使用這些工具去獲取有用的信息。而不是像許多開發人員那樣,只是滿足于高覆蓋率。
測試覆蓋工具通?梢院苋菀椎靥砑拥酱_定的單元測試過程中,而且結果可靠。下載一個可用的工具,對您的 Ant 和 Maven 構建腳本作一些小的改動,您和您的同事就有了在飲水機邊上談論的一種新報告:測試覆蓋報告。當 foo
和 bar
這樣的程序包令人驚奇地顯示高 覆蓋率時,您可以得到不小的安慰。如果您相信至少您的部分代碼可以保證是 “沒有 BUG” 的,您會覺得很安心。但是這樣做是一個錯誤。
存在不同類型的覆蓋度量,但是絕大多數的工具會關注行覆蓋,也叫做語句覆蓋。此外,有些工具會報告分支覆蓋。通過用一個測試工具執行代碼庫并捕獲整個測試過程中與被 “觸及” 的代碼對應的數據,就可以獲得測試覆蓋度量。然后這些數據被合成為覆蓋報告。在 Java 世界中,這個測試工具通常是 JUnit 以及名為 Cobertura、Emma 或 Clover 等的覆蓋工具。
行覆蓋只是指出代碼的哪些行被執行。如果一個方法有 10 行代碼,其中的 8 行在測試中被執行,那么這個方法的行覆蓋率是 80%。這個過程在總體層次上也工作得很好:如果一個類有 100 行代碼,其中的 45 行被觸及,那么這個類的行覆蓋率就是 45%。同樣,如果一個代碼庫包含 10000 個非注釋性的代碼行,在特定的測試運行中有 3500 行被執行,那么這段代碼的行覆蓋率就是 35%。
報告分支覆蓋 的工具試圖度量決策點(比如包含邏輯 AND
或 OR
的條件塊)的覆蓋率。與行覆蓋一樣,如果在特定方法中有兩個分支,并且兩個分支在測試中都被覆蓋,那么您可以說這個方法有 100% 的分支覆蓋率。
問題是,這些度量有什么用?很明顯,很容易獲得所有這些信息,不過您需要知道如何使用它們。一些例子可以闡明我的觀點。
我在清單 1 中創建了一個簡單的類以具體表述類層次的概念。一個給定的類可以有一連串的父類,例如 Vector
,它的父類是 AbstractList
,AbstractList
的父類又是 AbstractCollection
,AbstractCollection
的父類又是 Object
:
清單 1. 表現類層次的類
package com.vanward.adana.hierarchy;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Hierarchy { private Collection classes; private Class baseClass; public Hierarchy() { super(); this.classes = new ArrayList(); } public void addClass(final Class clzz){ this.classes.add(clzz); } /** * @return an array of class names as Strings */ public String[] getHierarchyClassNames(){ final String[] names = new String[this.classes.size()]; int x = 0; for(Iterator iter = this.classes.iterator(); iter.hasNext();){ Class clzz = (Class)iter.next(); names[x++] = clzz.getName(); } return names; } public Class getBaseClass() { return baseClass; } public void setBaseClass(final Class baseClass) { this.baseClass = baseClass; }} |
正如您看到的,清單 1 中的 Hierarchy
類具有一個 baseClass
實例以及它的父類的集合。清單 2 中的 HierarchyBuilder
通過兩個復制 buildHierarchy
的重載的 static
方法創建了 Hierarchy
類。
清單 2. 類層次生成器
package com.vanward.adana.hierarchy;public class HierarchyBuilder { private HierarchyBuilder() { super(); } public static Hierarchy buildHierarchy(final String clzzName) throws ClassNotFoundException{ final Class clzz = Class.forName(clzzName, false, HierarchyBuilder.class.getClassLoader()); return buildHierarchy(clzz); } public static Hierarchy buildHierarchy(Class clzz){ if(clzz == null){ throw new RuntimeException("Class parameter can not be null"); } final Hierarchy hier = new Hierarchy(); hier.setBaseClass(clzz); final Class superclass = clzz.getSuperclass(); if(superclass != null && superclass.getName().equals("java.lang.Object")){ return hier; }else{ while((clzz.getSuperclass() != null) && (!clzz.getSuperclass().getName().equals("java.lang.Object"))){ clzz = clzz.getSuperclass(); hier.addClass(clzz); } return hier; } } } |
有關測試覆蓋的文章怎么能缺少測試案例呢?在清單 3 中,我定義了一個簡單的有三個測試案例的 JUnit 測試類,它將試圖執行 Hierarchy
類和 HierarchyBuilder
類:
清單 3. 測試 HierarchyBuilder!
package test.com.vanward.adana.hierarchy;import com.vanward.adana.hierarchy.Hierarchy;import com.vanward.adana.hierarchy.HierarchyBuilder;import junit.framework.TestCase;public class HierarchyBuilderTest extends TestCase { public void testBuildHierarchyValueNotNull() { Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class); assertNotNull("object was null", hier); } public void testBuildHierarchyName() { Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class); assertEquals("should be junit.framework.Assert", "junit.framework.Assert", hier.getHierarchyClassNames()[1]); } public void testBuildHierarchyNameAgain() { Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class); assertEquals("should be junit.framework.TestCase", "junit.framework.TestCase", hier.getHierarchyClassNames()[0]); } } |
因為我是一個狂熱的測試人員,我自然希望運行一些覆蓋測試。對于 Java 開發人員可用的代碼覆蓋工具中,我比較喜歡用 Cobertura,因為它的報告很友好。而且,Corbertura 是開放源碼項目,它派生出了 JCoverage 項目的前身。
文章來源于領測軟件測試網 http://www.kjueaiud.com/