JUnit4 源碼 之 Runner
- Runner 抽象類
- ParentRunner & BlockJUnit4ClassRunner 基本實現
- Suite 實現原理
- 擴展閱讀
Runner抽象類
Runner抽象類的最爲重要的方法就是run方法,這點 從JUnitCore中就可以看出來
// from JUnitCore -- 從JUnitCore(測試驅動類)上看Runner
public Result run(Runner runner) { runner.run(notifier); ...... } // 只關心Runner接口的run方法
ParentRunner & BlockJUnit4ClassRunner 基本實現
接着 嘗試去找run方法的實現,在ParentRunner中發現了
@Override
public void run(final RunNotifier notifier) {
Statement statement = classBlock(notifier);
statement.evaluate();
}
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement); // 使用Statement對@BeforeClass邏輯進行包裝
statement = withAfterClasses(statement); // 使用Statement對@AfterClass邏輯進行包裝
statement = withClassRules(statement); // 使用Statement對@ClassRule邏輯進行包裝
}
return statement;
}
protected Statement childrenInvoker(final RunNotifier notifier) {
// 創建Statement對象,但並不觸發執行
return new Statement() {
@Override
public void evaluate() { runChildren(notifier); }
};
}
private void runChildren(final RunNotifier notifier) {
// scheduler 在默認情況下是 單線程直接點用,所以會表現出同步阻塞現象
// 可以將scheduler 改造成Parallel 模式的,即內部調度換成線程池
final RunnerScheduler currentScheduler = scheduler;
try {
for (final T each : getFilteredChildren()) {
currentScheduler.schedule(new Runnable() {
// 語法解釋: 調用ParentRunner實現類的runChild方法
public void run() { ParentRunner.this.runChild(each, notifier); }
});
}
} finally { currentScheduler.finished(); }
}
從上面分析中已經知道
- 針對BeforeClass & AfterClass的“包裝實現邏輯(Statement)”封裝在ParentRunner中
- children是迭代調度邏輯也封裝在ParentRunner中
- 各個child的內部“上下文邏輯封裝 & 調度” 由ParentRunner子類實現
- Before & After & Test中(timeout & excepted) & Rule 的包裝邏輯(Statement)
接着 觀察ParentRunner 的其中一個實現類 BlockJUnit4ClassRunner的 runChild實現
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) { notifier.fireTestIgnored(description); }
// 通過methodBlock 包裝Before & After & Test(timeout & excepted) & Rule的Statement邏輯
// 並通過runLeaf 觸發該Statement
// 其中Leaf,可以表明 JUnit將每一個測試用例方法抽象理解成了Leaf,通過這裏,可以觀察出 JUnit整體的模型設計爲 Tree
else { runLeaf(methodBlock(method), description, notifier); }
}
protected Statement methodBlock(FrameworkMethod method) {
Object test;
try {
// createTest:getTestClass().getOnlyConstructor().newInstance()
// 通過反射調用 來構建test instance
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable { return createTest(); }
}.run();
} catch (Throwable e) { return new Fail(e); }
Statement statement = methodInvoker(method, test); // 使用Statement對@Test邏輯進行包裝
// 使用Statement對@Test excepted邏輯進行包裝
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement); // 使用Statement對@Test timeout邏輯進行包裝
statement = withBefores(method, test, statement); // 使用Statement對@Before邏輯進行包裝
statement = withAfters(method, test, statement); // 使用Statement對@Before邏輯進行包裝
statement = withRules(method, test, statement); // 使用Statement對@Rule邏輯進行包裝
return statement;
}
protectedfinal void runLeaf(Statement statement, Description description, RunNotifier notifier) {
......
statement.evaluate(); // 觸發測試用例方法
}
在JUnit4中提供的Parameterized中,使用的Leaf Runner並不是BlockJUnit4ClassRunner,而是繼承了BlockJUnit4ClassRunner 的 BlockJUnit4ClassRunnerWithParameters,其中最主要的是對對 createTest的Override
// 首先觀察一下BlockJUnit4ClassRunner 的 createTest
protected Object createTest() throws Exception {
return getTestClass().getOnlyConstructor().newInstance();
}
// 接着 觀察一下BlockJUnit4ClassRunnerWithParameters 的 createTest
@Override
public Object createTest() throws Exception {
// 兩種實現方式,一般本人採用第二種,這裏也只展示第二種
if (fieldsAreAnnotated()) { return createTestUsingFieldInjection(); }
else { return createTestUsingConstructorInjection(); }
}
private Object createTestUsingConstructorInjection() throws Exception {
// 其中parameters 爲通過@Parameterized 提供的參數
return getTestClass().getOnlyConstructor().newInstance(parameters);
}
Suite 實現原理
通過之前介紹的AnnotatedBuilder的源碼,可以發現 任何通過@RunWith來標識的測試用例類 運行時傳遞至JUnitCore的Runner 都是通過RunWith註解中的Class類創建的。
於是 觀察一下Suite的構造函數,可以很直觀的發現它到底做了些什麼
public Suite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
this(builder, klass, getAnnotatedClasses(klass));
}
private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class);
if (annotation == null) {
throw new InitializationError(
String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
}
return annotation.value();
}
即通過讀取SuiteClasses註解中標記的類,來作爲需要掃描的測試用例類 – 是Class數組
接着 通過RunnerBuilder的 runners方法(針對多test class來迭代構建runners)來構建多個TestClass的全部runners
接着的邏輯 就是ParentRunner的邏輯了,就這麼簡單
Parameterized 的實現邏輯 可以自行閱讀
擴展閱讀
https://github.com/PeterWippermann/parameterized-suite – ParameterizedSuite
https://github.com/diva-e/parallel-test-runner – ParallelTestRunner
還有就是SpringJUnit4ClassRunner – 有助於學習Spring