JUnit4 源碼 之 Runner

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章