想像一下,您是一家 2002 年創立公司的開發人員。當時滿懷熱血,團隊決定採用最新的 Java API 構建大型數據驅動 Web 應用,並全盤擁抱剛興起的「敏捷開發」理念。從專案第一天起,JUnit 測試便融入 Ant 構建腳本,頻繁執行;夜間更有定時任務跑全量測試。後來引入 CruiseControl,每次提交代碼都會自動觸發測試套件運行。一切看似美好。
幾年過去,代碼庫龐大,JUnit 測試套件也膨脹到 2000 多個測試案例。構建時間逐漸超過三小時。由於 CI 服務器資源有限,團隊不得不取消提交時的單元測試,改為夜間跑測試。第二天早上,開發人員花費大量時間診斷失敗原因。久而久之,測試幾乎不再全天候運行——因為太耗時了,沒人願意守著等結果。測試頻率降低導致錯誤堆積,團隊開始懷疑單元測試的價值:既然這麼重要,為什麼卻讓人這麼頭痛?結論很明確:單元測試不可或缺,但必須找到更敏捷的運行方式。
要將構建恢復敏捷,需要一種策略:讓測試一天跑多次,並將構建時間從三小時大幅縮減。在此之前,必須先釐清「單元測試」的具體含義。就像說「我家有動物」或「我喜歡車」過於籠統,「我們寫單元測試」同樣模糊。動物可能是貓、蜥蜴或熊;車可能是跑車、卡車或旅行車。開發人員測試亦然,按類型分類才能用更精確的語言溝通,並以不同頻率運行不同測試——這正是避開三小時構建恐懼的關鍵。
測試套件可形象劃分為三層,每層代表不同類型的開發人員測試,由執行時間長短決定(如圖 1 所示)。每一層都增加總構建時間,或是運行時間,或是編寫時間。

單元測試隔離驗證一個或多個對象,不涉及數據庫、文件系統等外部依賴,因此從項目伊始即可編寫。這正是 JUnit 的設計初衷。無數模擬對象庫(Mock 框架)支撐著隔離概念,方便將特定對象從外部依賴中剝離。單元測試甚至可在實現代碼前編寫,催生了「測試優先開發」(TDD)。
優勢:易寫、跑得極快、不依賴架構依賴。缺點:單一測試覆蓋面有限。核心價值在於讓開發人員在最底層保證對象可靠性。正因如此,代碼庫應包含大量單元測試,並在構建時、CI 環境中(代碼提交即觸發)頻繁運行。
組件測試驗證多個交互對象,打破隔離界限,涉及數據庫、文件系統、網絡等架構層次。因此較難提前編寫,納入純粹 TDD 場景挑戰較大。編寫耗時較長,但覆蓋率遠超單元測試;執行時間也顯著增加。DbUnit 等框架能簡化數據庫狀態管理,使依賴數據庫的測試更易編寫。
當構建時間飆升,通常預示著龐大的組件測試套件。這類測試不適合持續運行。建議:CI 環境中至少每小時跑一次;開發人員提交代碼前,在本機務必跑通相關組件測試。
系統測試端到端驗證整個軟件應用,引入最高架構複雜度:Web 應用需啟動數據庫、Web 服務器、容器及完整配置。多在軟件生命周期後期編寫。編寫困難、執行耗時,但從架構代碼覆蓋率看極具性價比。與功能測試相似,區別在於系統測試模擬用戶而非仿效用戶。jWebUnit 等框架通過模擬瀏覽器測試 Web 應用。
jWebUnit vs Selenium:jWebUnit 是針對系統測試的 JUnit 擴展,需程序員編寫測試;Selenium 擅長驗收/功能測試,非程序員也能編寫。理想情況下,團隊同時使用兩者驗證功能。
假設您的測試套件混雜了三類測試,且組件測試佔用了絕大多數時間。如何用 JUnit 實現分類?這裡介紹兩種最簡單的方式:
利用 JUnit TestSuite 類聚合同類測試。創建 UnitTestSuite、ComponentTestSuite、SystemTestSuite,各自通過 suite() 靜態方法返回包含的測試類。例如清單 1 展示組件測試套件:
package test.org.acme.widget;
import junit.framework.Test;
import junit.framework.TestSuite;
import test.org.acme.widget.*;
public class ComponentTestSuite {
public static void main(String[] args) {
junit.textui.TestRunner.run(ComponentTestSuite.suite());
}
public static Test suite(){
TestSuite suite = new TestSuite();
suite.addTestSuite(DefaultSpringWidgetDAOImplTest.class);
suite.addTestSuite(WidgetDAOImplLoadTest.class);
// ...
suite.addTestSuite(WidgetReportTest.class);
return suite;
}
}
再定義 Ant 任務分別調用三類套件(清單 2 為組件測試任務),並創建 test-all 任務依賴三者(清單 3)。缺點:新增測試時必須手動修改套件類並重新編譯,維護成本高。
將測試按類型放入對應目錄:
acme-proj/
test/
unit/
component/
system/
conf/
Ant 任務的 batchtest 指向具體目錄(清單 5 針對 test/unit)。新增測試只需放入對應目錄,無需修改代碼、重新編譯,擴展性極佳。
團隊採用目錄分類方案,重構 Ant 腳本創建四個任務(三類單獨任務 + 全量任務),修改 CruiseControl:提交時只跑單元測試,每小時跑組件測試,系統測試同頻運行,並創建組合任務同時跑組件與系統測試。最終測試每天多次運行,集成錯誤通常在幾小時內被發現。敏捷構建雖未解決所有問題,卻在保障代碼質量上扮演關鍵角色。2006 年,公司業務大獲成功!