JUnit是Java生態事實標準的單元測試框架。其設計蘊含豐富的設計模式智慧:Command、Template Method、Collecting Parameter、Adapter、Pluggable Selector、Composite。理解這些模式在JUnit中的應用,不僅能用好框架,更能提升架構設計能力。本文基於JUnit 3.x經典架構(JUnit 4/5保留核心模式),深度剖析框架演進邏輯。
將「一個測試」封裝為對象,而非腳本/打印語句/調試器表達式。TestCase實現Test接口,提供run(TestResult)執行入口,對應Command模式的execute()。優勢:
TestCase實例擁有自己的fixture(夾具)
public abstract class TestCase implements Test {
private final String fName; // 測試名稱,失敗時定位
public TestCase(String name) { fName = name; }
public abstract void run(TestResult result); // Command入口
}
所有測試共享相同生命周期:setUp → runTest → tearDown。TestCase在run()中定義骨架,子類覆寫具體步驟:
public void run(TestResult result) {
result.startTest(this);
setUp(); // 建立fixture
try {
runTest(); // 核心測試邏輯
} catch (AssertionFailedError e) {
result.addFailure(this, e);
} catch (Throwable e) {
result.addError(this, e);
} finally {
tearDown(); // 清理fixture,保證執行
}
}
protected void setUp() {}
protected void tearDown() {}
protected void runTest() {} // 默認空,Pluggable Selector機制實現
Template Method確保執行順序固定、fixture隔離(每測試新實例),子類專注業務邏輯。
避免在每個測試方法簽名加TestResult參數(污染API),改用異常傳遞失敗信息,框架內部收集:
public class TestResult {
protected int fRunTests;
protected Vector<TestFailure> fFailures = new Vector<>();
protected Vector<TestFailure> fErrors = new Vector<>();
public synchronized void startTest(Test test) { fRunTests++; }
public synchronized void addFailure(Test test, Throwable t) { fFailures.add(new TestFailure(test, t)); }
public synchronized void addError(Test test, Throwable t) { fErrors.add(new TestFailure(test, t)); }
}
TestFailure綁定失敗的測試+異常對象。synchronized保證多線程收集安全。擴展點:自定義HTMLTestResult、UITestResult(圖形化進度條)。
AssertionFailedError(繼承Error,非Exception,繞過檢查異常捕獲)NullPointerException、ArrayIndexOutOfBounds等)斷言方法(assertTrue、assertEquals、assertNull...)拋AssertionFailedError,由run()捕獲轉為Failure。測試代碼無需try-catch,框架統一處理。
問題:每個測試方法需一個TestCase子類?類爆炸!
TestCase test = new MoneyTest("testMoneyEquals") {
protected void runTest() { testMoneyEquals(); }
};
protected void runTest() throws Throwable {
Method m = getClass().getMethod(fName, new Class[0]); // 查找同名public方法
m.invoke(this, new Object[0]);
}
約定:構造器傳入方法名("testMoneyEquals"),反射調用。要求測試方法public、無參。JUnit 4改用註解@Test,原理同。
統一Test接口,TestCase為Leaf,TestSuite為Composite:
public interface Test {
void run(TestResult result);
}
public class TestSuite implements Test {
private Vector<Test> fTests = new Vector<>();
public void addTest(Test test) { fTests.add(test); }
public void run(TestResult result) {
for (Test test : fTests) test.run(result); // 遞歸委派
}
}
便捷構造器:new TestSuite(MoneyTest.class) → 反射發現所有test*方法自動添加。支持嵌套Suite構建任意層級測試樹。
TestCase周圍聚集6大模式,形成高模式密度區,體現成熟框架核心抽象的豐富關係網。關鍵洞見:
TestDecorator等)| 模塊 | JUnit 3/4 | JUnit 5 (Jupiter) |
|---|---|---|
| 發現機制 | 反射掃描test*/@Test | TestEngine API,支持參數化、動態測試 |
| 生命周期 | setUp/tearDown | @BeforeEach/@AfterEach/@BeforeAll/@AfterAll |
| 斷言 | Assert.* | Assertions.* + assertAll、assertThrows、assertTimeout |
| 擴展模型 | TestDecorator | Extension API(參數解析器、條件執行、回調) |
| 架構 | 單一jar | Platform + Jupiter + Vintage(兼容舊版) |
JUnit不只是測試工具,更是設計模式教科書。從Command封裝測試、Template Method固化生命周期、Collecting Parameter收集結果、Adapter/Pluggable Selector適配方法、Composite組合套件,每個模式精準解決一個架構問題。現代JUnit 5保留核心哲學,以Extension API替代舊擴展機制,支撐參數化測試、動態測試、並行執行等進階需求。深入理解JUnit架構,能讓你在測試代碼、業務代碼、框架代碼中游刃有餘地應用模式思維。