JUnit4之BlockJUnit4ClassRunner

Runner概述

JUnit對Runner的定位爲負責執行測試方法和通知測試的Listener

可以通過@Runwith來執行自定義Runner

在每次執行測試方法之前都會通過反射創建一個新的測試類對象,這會導致測試類中的成員變量無法在每個測試方法調用中保持相同的值(需要在@Before和@After中進行重置) , 在JUnit5中可以通過@TestInstance(Lifecycle.PER_CLASS)來實現測試類的單例


BlockJunit4ClassRunner繼承圖




ParentRunner介紹

ParentRunner可以看作所有runner的父節點,其下有許多子節點作爲具體的測試方法來執行

在ParentRunner實例化時會對包含特定註解的方法進行方法參數、修飾符、返回值的校驗

比如@Before或@AfterClass註解的方法,從方法名validatePublicVoidNoArgMethods可見一斑

    protected ParentRunner(TestClass testClass) throws InitializationError {
       this.testClass = notNull(testClass);
       validate();
    }
    private void validate() throws InitializationError {
        List<Throwable> errors = new ArrayList<Throwable>();
        collectInitializationErrors(errors);
        if (!errors.isEmpty()) {
            throw new InvalidTestClassError(testClass.getJavaClass(), errors);
        }
    }
    protected void collectInitializationErrors(List<Throwable> errors) {
        validatePublicVoidNoArgMethods(BeforeClass.class, true, errors);
        validatePublicVoidNoArgMethods(AfterClass.class, true, errors);
        validateClassRules(errors);
        applyValidators(errors);
    }


在ParentRunner中定義了run方法規定了整個測試的執行流程,並留下了三個抽象方法交給子類去實現具體邏輯

分別是

    /**
     * Returns a list of objects that define the children of this Runner.
     */
    protected abstract List<T> getChildren();

    /**
     * Returns a {@link Description} for {@code child}, which can be assumed to
     * be an element of the list returned by {@link ParentRunner#getChildren()}
     */
    protected abstract Description describeChild(T child);

    /**
     * Runs the test corresponding to {@code child}, which can be assumed to be
     * an element of the list returned by {@link ParentRunner#getChildren()}.
     * Subclasses are responsible for making sure that relevant test events are
     * reported through {@code notifier}
     */
    protected abstract void runChild(T child, RunNotifier notifier);


在啓動JUnitCore時,會調用ParentRunner#run(RunNotifier):

    public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        testNotifier.fireTestSuiteStarted();
        try {
            Statement statement = classBlock(notifier);
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            testNotifier.addFailedAssumption(e);
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            testNotifier.addFailure(e);
        } finally {
            testNotifier.fireTestSuiteFinished();
        }
    }

主要做了兩件事

1、通知事件監聽器測試流程仙谷歌那的事件

2、調用classBlock(notifier)構造並調用Statement執行測試


classBlock()負責構造這樣一個Statement:

1、通過註解校驗和過濾篩選出需要執行的測試方法

2、對這些測試方法使用裝飾器模式進行 @BeforeClass 和 @AfterClass和@ClassRule的包裝

    protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);
            statement = withAfterClasses(statement);
            statement = withClassRules(statement);
        }
        return statement;
    }

childrenInvoker(notifier)構造了一個匿名內部類,直接調用runChildren()

    protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                runChildren(notifier);
            }
        };
    }
runChildren()則調用getFilteredChildren()方法(會調用子類的getChildren()掃描JUnit註解,比如@Test),之後通過RunnerScheduler直接調用子類實現的抽象方法runChild()

    private void runChildren(final RunNotifier notifier) {
        final RunnerScheduler currentScheduler = scheduler;
        try {
            for (final T each : getFilteredChildren()) {
                currentScheduler.schedule(new Runnable() {
                    public void run() {
                        ParentRunner.this.runChild(each, notifier);
                    }
                });
            }
        } finally {
            currentScheduler.finished();
        }
    }
ParentRunner中的RunnerScheduler實現如下:直接調用了run方法

    private volatile RunnerScheduler scheduler = new RunnerScheduler() {
        public void schedule(Runnable childStatement) {
            childStatement.run();
        }

        public void finished() {
            // do nothing
        }
    };


BlockJUnit4ClassRunner介紹

BlockJUnit4ClassRunner作爲JUnit4的默認Runner實現,在Runner執行過程中對ParentRunner的三個抽象方法實現了自己的特定邏輯

getChildren() : 反射掃描獲取@Test註解的方法

protected List<FrameworkMethod> getChildren() {
        return computeTestMethods();
}
protected List<FrameworkMethod> computeTestMethods() {
        return getTestClass().getAnnotatedMethods(Test.class);
}


describeChild() : 對測試方法創建Description並進行緩存

protected Description describeChild(FrameworkMethod method) {
        Description description = methodDescriptions.get(method);

        if (description == null) {
            description = Description.createTestDescription(getTestClass().getJavaClass(),
                    testName(method), method.getAnnotations());
            methodDescriptions.putIfAbsent(method, description);
        }

        return description;
}

runChild() : 
1、調用describeChild()
2、判斷方法是否包含@Ignore註解,有就觸發TestIgnored事件通知
3、構造Statement回調,通過methodBlock()構造並裝飾測試方法
4、執行測試方法調用statement.evaluate()

    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        if (isIgnored(method)) {
            notifier.fireTestIgnored(description);
        } else {
            Statement statement = new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    methodBlock(method).evaluate();
                }
            };
            runLeaf(statement, description, notifier);
        }
    }

methodBlock() :
1、通過反射新建一個測試類的實例createTest(),所以每個@Test執行的時候的測試類都是一個新的對象

protected Object createTest() throws Exception {
        return getTestClass().getOnlyConstructor().newInstance();
    }
2、調用methodInvoker()構造一個InvokeMethod,作用是通過反射執行方法
3、對以下註解的處理

1、@Test(expected=RunTimeException.class)可能存在的異常的處理

如果指定了expected,則包裝一層ExpectException,是Statement的子類

2、@Test(timeout=10),對超時時間的指定,包裝一層FailOnTimeout

3、@Before,包裝RunBefores

4、@After,包裝RunAfters

5、@Rule,根據Rule的順序繼續包裝

Rule.apply()會返回Statement,作用是對執行流程進行自定義擴展或者說是制定自定義規則


  protected Statement methodBlock(final FrameworkMethod method) {
        Object test;
        try {
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest(method);
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }

        Statement statement = methodInvoker(method, test);
        statement = possiblyExpectingExceptions(method, test, statement);
        statement = withPotentialTimeout(method, test, statement);
        statement = withBefores(method, test, statement);
        statement = withAfters(method, test, statement);
        statement = withRules(method, test, statement);
        return statement;
    }



runLeaf(statement, description, notifier); 最終調用Statement.evaluate()開始執行測試方法

    protected final void runLeaf(Statement statement, Description description,
            RunNotifier notifier) {
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
        eachNotifier.fireTestStarted();
        try {
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            eachNotifier.addFailure(e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }


關於RunnerBuillder

RunnerBuilder的作用是構造不同的Runner,JUnit4中可以分爲兩類,一類根據JUnit4自己定義的註解來構造Runner,一類是用於用戶自定義擴展的,比如@Runwith註解的AnnotatedBuilder

JUnit4中提供的工具類爲AllDefaultPossibilitiesBuilder,它包含了所有默認的RunnerBuilder實現
public Runner runnerForClass(Class<?> testClass) throws Throwable {
        List<RunnerBuilder> builders = Arrays.asList(
                ignoredBuilder(),
                annotatedBuilder(),
                suiteMethodBuilder(),
                junit3Builder(),
                junit4Builder());

        for (RunnerBuilder each : builders) {
            Runner runner = each.safeRunnerForClass(testClass);
            if (runner != null) {
                return runner;
            }
        }
        return null;
    }





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