JUnit 作為單元測試領域的基石框架,其源碼蘊含豐富的設計模式智慧。本文基於 JUnit 3.8 源碼,從生命周期、核心類結構、三大設計模式應用三個維度進行深度剖析,適合進階 Java 開發者與測試工程師閱讀。
完整生命周期分為三階段:
junit.textui.TestRunner.main() → start(args) → getTest(className)
suiteMethod.invoke(null) 獲取用戶組裝的 TestSuite。new TestSuite(testClass),構造器掃描類及父類所有 public void testXXX() 方法。public interface Test {
int countTestCases();
void run(TestResult result);
}
public class TestSuite implements Test {
private Vector fTests = new Vector<>(10); // 子樹或葉子
public void addTest(Test test) { fTests.addElement(test); }
public void run(TestResult result) {
for (Enumeration e = tests(); e.hasMoreElements(); ) {
Test test = (Test) e.nextElement();
test.run(result); // 遞歸運行,統一處理葉子與子樹
}
}
}
public class TestCase implements Test {
// 葉子節點,具體測試方法載體
}
優勢:統一接口 Test 使客戶代碼無需區分單個測試與測試套件,實現「部分-整體」層次結構的透明操作,支持無限嵌套組合。
Test 接口即命令接口,TestCase 和 TestSuite 是具體命令。TestResult.run(Test) 作為調用者,無需知道具體測試邏輯,只需調用 run()。新增測試類型只需實現 Test 接口,符合開閉原則。
// TestCase.runBare() -> runTest()
protected void runTest() throws Throwable {
// fName 在創建 TestCase 時設置為方法名
Method runMethod = getClass().getMethod(fName, (Class[]) null);
runMethod.invoke(this, (Object[]) null); // 反射調用具體 testXXX()
}
配合 setUp()/tearDown()(模板方法模式),tearDown() 放在 finally 塊保證執行。
public void runProtected(Test test, Protectable p) {
try { p.protect(); } // 實際調用 test.runBare()
catch (AssertionFailedError e) { addFailure(test, e); }
catch (ThreadDeath e) { throw e; }
catch (Throwable e) { addError(test, e); }
}
Protectable 是函數對象模式,將 test.runBare() 封裝為 protect() 調用,統一異常攔截轉換為 Failure/Error。
public interface TestListener {
void startTest(Test test);
void endTest(Test test);
void addFailure(Test test, AssertionFailedError t);
void addError(Test test, Throwable t);
}
public class TestResult {
private Vector fListeners = new Vector<>();
public synchronized void addListener(TestListener listener) { ... }
// 內部通知所有監聽器
private void notifyListeners(Runnable notification) { ... }
}
ResultPrinter(文本界面):實時打印 . F E,最後統計汇總。TestRunner(圖形界面/Swing):綠/紅進度條、樹形結果展示。解耦效果:TestResult 零依賴具體報告形式,新增報告方式只需實現 TestListener,無需修改核心代碼。
| 模式 | 角色映射 | 核心價值 |
|---|---|---|
| Composite | Component=Test, Leaf=TestCase, Composite=TestSuite | 統一單測與套件操作,支持無限嵌套組合 |
| Command | Command=Test, ConcreteCommand=TestCase/TestSuite, Invoker=TestResult | 解耦調用與執行,易擴展新測試類型,可結合 Memento 實現 Undo |
| Observer | Subject=TestResult, Observer=TestListener, ConcreteObserver=ResultPrinter/UI | 運行核心與報告呈現完全分離,支持多樣化輸出 |
JUnit 大量使用 Class.getMethod()、Method.invoke()、Constructor.newInstance() 動態發現/實例化/調用測試代碼,展示了 Java 反射在框架開發中的核心地位:框架調用應用代碼,而非應用調用框架(控制反轉雛形)。
JUnit 用 <2000 行代碼實現了工業級測試框架,其精髓在於:
這些模式組合思想,對設計任何可擴展框架(插件系統、工作流引擎、規則引擎)都有極高參考價值。