JUnit4之BlockJUnit4ClassRunner

来源:互联网 发布:网络精灵pipopa中文版 编辑:程序博客网 时间:2024/06/06 18:12

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;    }





原创粉丝点击