單元測試和靜態(tài)分析通常被看作是有助于確保程序的正確性的互不相干的方法。本文研究了這兩種方法之間的關系,并討論了構成每種方法工作構架的工具如何相得益彰。特別地,Eric Allen 討論了一些可用而又令人興奮的新應用程序,這些應用程序允許您進一步提升您的單元測試。
這是一場古老的爭論 — 哪種方法對產(chǎn)生健壯代碼更有價值:測試還是靜態(tài)分析和驗證?您會在程序員的日常工作中聽到這種爭論,尤其是在極端編程(Extreme Programming)CC%B3');" target="_self">論壇上。(請參閱我們由 Roy Miller 主持的 XP 論壇。)
支持靜態(tài)分析(包括類型檢查)的主要論據(jù)是:其結果適用程序所有可能的運行,而通過單元測試只能保證被測試的組件(在測試它們的平臺上)只適用測試組件的特定輸入。
支持單元測試的主要論據(jù)是它更容易處理。您可以測試程序的許多約束,這些約束遠遠超出了同期的靜態(tài)分析工具所能達到的范圍。
請允許我在此冒昧地說一句:我認為將這兩種工具看作對立的是一個錯誤。每種工具都有助于構建更健壯的程序。實際上,它們可以通過非常強大的方式進行互補。
每種工具都有各自的長處,對于補充另一種工具特別有用:
單元測試能顯示執(zhí)行的常用路徑,從而顯示程序是如何運行的。
分析工具能檢查單元測試提供的覆蓋范圍。
讓我們研究這其中的每個屬性,并討論一些可幫助您將其長處帶給其它方法的工具。
顯示常用執(zhí)行路徑的單元測試
單元測試套件提供了程序組件的示例用法的穩(wěn)固基礎。通過檢查測試運行時程序是如何運作的,分析工具可以開發(fā)人員希望在程序中保持的不變量進行試探性推測(和程序員閱讀單元測試所做的一樣)。
還有另一種方法,其中單元測試可以是一種可執(zhí)行的文檔形式。在從單元測試的運行中從特殊到一般地推斷出推測性不變量之后,分析工具可以嘗試從一般到特殊地驗證不變量的存在,或者它可以利用可在運行時檢查的斷言注釋該代碼。
在任何一種情況下,在該工具做任何其它工作之前,好向用戶返回推測的不變量集的報告,以詢問用戶真正想要哪些不變量。順便提一下,如果此類工具向用戶報告了許多他們不想要的不變量,這可能是單元測試出了問題的信號 — 例如,它們不夠一般。
可用這種方式與單元測試一起使用的工具是 Daikon,它是一款來自 MIT 的 Mike Ernst 的程序分析小組的免費的、試驗性的工具。Daikon 分析程序的運行(例如單元測試的運行),并嘗試推測不變量。然后它詢問用戶是否想要這些不變量,并將用戶想要的不變量作為斷言插入程序。
例如,假定我們編寫一個向量(Vector)的適配器,該適配器實現(xiàn)接口 Sequence,該接口包含用于檢索元素的方法 lookup 和用于將元素放在向量末尾的方法 insert。方法 lookup 帶有一個索引 i,用來訪問它所包含的向量。
假定該數(shù)組的長度存儲在字段 length 中。通過維護適配器中的長度,我們可以不通知向量本身將元素從其尾部刪除。
讓我們?yōu)檫@個假想的簡單適配器編寫一個簡單的測試用例:
清單 1. 向量容器中簡單查找方法的測試用例
clearcase/" target="_blank" >cccccc height=17>import junit.framework.TestCase;
public class VectorAdapterTest extends TestCase {
public VectorAdapterTest(String name) {
super(name);
}
public void testLookupAndInsert() {
VectorAdapter v = new VectorAdapter();
v.insert("this");
v.insert("is");
v.insert("a");
v.insert("test");
assertEquals("Retrieved and inserted elements don't match",
"a",
v.lookup(2));
}
}
然后我們可以實現(xiàn)我們的適配器以通過這個測試,如下所示:
清單 2. 類 VectorAdapter
import java.util.Vector;
public class VectorAdapter implements Sequence {
private Vector values = new Vector();
private int length = 0;
public void insert(Object o) {
length += 1;
values.addElement(o);
}
public Object lookup(int i) {
return values.elementAt(i);
}
}
interface Sequence {
public void insert(Object o);
public Object lookup(int i);
}
當 Daikon 在這段代碼上運行時,它可能推斷:對于方法 lookup,i 總是小于 length。Daikon 可能從單元測試中推斷出這一點,并向我們的方法報告一條前置條件:i < length。
然后程序員可以檢查 Daikon 報告的不變量,從而更好地了解其測試覆蓋程序的范圍到底怎么樣。例如,如果 Daikon 開始推斷出大量不想要的不變量,這意味著單元測試只是用不具代表性的可能的程序輸入的子集檢測了程序。
盡管 Daikon 是用 Java 語言編寫的,但它需要用 C++ 編寫的前端,這削弱了它原有的可移植性。盡管如此,還是可以在線獲得針對許多主要平臺的前端構建。此外,Daikon 團隊也打算添加其它平臺所需要的構建。
(您可以在參考資料一節(jié)找到關于 Daikon 的下載信息和更多內容。)