JUnit 作為 Java 單元測試的基石框架,雖然上手容易,但要寫出可維護、可靠的測試代碼卻需要遵循一系列最佳實踐。本文總結了十二條來自實戰的核心經驗,幫助開發者避開常見陷阱,提升測試代碼質量。
在測試類的構造函數中編寫初始化邏輯是危險的。一旦初始化拋出異常(如 IllegalStateException),JUnit 只會報告 AssertionFailedError: cannot instantiate test case,堆疊追蹤信息極其混亂,難以定位具體錯誤行。
正確做法:重寫 setUp() 方法進行初始化。此時異常堆疊會清晰指向具體代碼行(如 dtc.java:34),大幅縮短排查時間。
JUnit 內部使用 Vector 存儲測試方法,執行順序在不同 JVM 和作業系統上不可預測。測試之間必須保持完全獨立,任何順序執行結果都應一致。
若確有順序依賴需求,可通過手動構建 TestSuite 並按序 addTest() 來強制順序:
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new SomeTestCase("testDoThisFirst"));
suite.addTest(new SomeTestCase("testDoThisSecond"));
return suite;
}
需要人工干預的測試無法納入自動化回歸測試流程,也無法重複執行。例如刪除操作測試,應在測試結束時自動恢復數據,確保測試之間無狀態殘留(自相關性)。
setUp()/tearDown() 必須調用父類實現繼承測試類時,子類的 setUp() 必須顯式調用 super.setUp(),確保父類定義的測試環境(如數據庫連接)被正確初始化。同理 tearDown() 也需調用 super.tearDown() 釋放資源。
硬編碼絕對路徑(如 c:\\testdata\\dataset1.dat)會導致跨平台、跨磁盤空間不足等問題。推薦使用 Class.getResourceAsStream() 從類路徑加載資源文件,將測試數據與源碼同步放在版本控制系統中,構建過程自動複製到編譯輸出目錄。
將測試代碼置於與主代碼相同的包結構下(如 src/main/java 對應 src/test/java),可確保兩者同步編譯、同步更新,體現「測試是構建流程一環」的現代工程理念。
Test{被測類名},如 TestMessageLogtest{具體場景},如 testLoggingEmptyMessage()、testLoggingNullMessage()良好的命名即文檔,讓閱讀者一眼理解測試意圖。
避免使用依賴默認 Locale 的日期解析(如 DateFormat.getInstance().parse("dd/mm/yyyy"))。應使用 Calendar 顯式構建日期對象,確保在任何地區設置下行為一致:
Calendar cal = Calendar.getInstance();
cal.set(yyyy, mm - 1, dd);
Date date = cal.getTime();
無需手動 try-catch 捕獲異常再調用 fail()。JUnit 會自動捕獲未處理異常並標記測試失敗。只需在方法簽名拋出受檢異常即可:
public void exampleTest() throws SomeApplicationException {
// do some test
}
註:若需驗證特定異常被拋出,則需保留斷言邏輯。
assertEquals("message", expected, actual) 比裸 assert(creds == 3) 可讀性強、失敗時信息豐富assertEquals("msg", expected, actual, delta)assertSame()(引用相同)與 assertEquals()(內容相等)避免使用會過期的測試數據;必要時可在測試前臨時修改系統時間,測試後恢復,但需警惕副作用。
而非手寫文檔。自動生成保證代碼與文檔同步,格式統一、錯誤率低。
這十二條經驗涵蓋了測試結構、獨立性、可移植性、可讀性、維護性等核心維度。將其內化為團隊規範,能顯著提升單元測試的工程質量與長期價值。