从执行流程分析Junit4源码

阅读前提条件,了解JUnit4的基本用法。代码版本: 3637550

从执行流程来分析

一般情况下使用IDE开发项目通过鼠标很容易执行测试方法和类,分析源码的话我们就要找到程序的入口,一步一步看程序是怎么跑起来的,下面我们先看如何在不依赖IDE的情况下让测试类跑起来。

手动运行方式

程序运行入口类:JUnitCore。该类有两种方式来执行测试类:

  1. mian方法:即最常见的public static void main(String... args),通过在命令行输入要执行测试的类作为参数来运行测试方法。

  2. 通过使用runClasses(Class... classes)方法,举个栗子:

 public class Test {
    public static void main(String[] args) {
        Result result = JUnitCore.runClasses(RunTest.class);  //RunTest即包含测试方法的类
        for (Failure failure : result.getFailures()) { // 对于执行失败的情况打印失败信息
            System.out.println(failure.toString());
        }
    }
}

两种方式的执行流程大致相同,最终都调用到了JUnitCore#run(Request request)方法,这里我们以第二种方式来分析执行流程。

开始分析

这里只分析了关键的代码和类,并没有贴出全部的代码,详细的内容要参考源码!

关键类(可以暂时忽略这些类,在调用流程分析中如果遇到哪个类不明白再返回来看说明):

  • Runner:用来执行测试,以RunNotifier的形式发布通知。

  • ParentRunner继承自Runner,提供了作为一个Runner的大部分功能,可以进行过滤和分类,处理BeforeClassAfterClass还有ClassRule,创建复合的Description,并按照顺序来执行多个测试。不过仍需要子类来构造并真正实现执行需要被Run的对象,

  • BlockJUnit4ClassRunner:继承自ParentRunner是默认的测试类的runner

  • Suite:作为一个runner,通过它可以包含多个类来进行测试执行。

  • RunnerBuilder:用来给测试类创建runner。

  • AllDefaultPossibilitiesBuilder: 主要是在runnerForClass(Class testClass)方法中根据注解标识返回合适的RunnerBuilder。比如,如过被测试的方法只被@Test标识,那么就会返回JUnit4Builder

  • JUnit4Builder:默认情况下被调用,创建返回BlockJUnit4ClassRunner实例。

  • Request :对被运行的测试类的抽象描述。

  • Computer:根据RunnerBuilder用来创建出runnersuite

  • Statement:一个接口,代表将要被运行的一个或多个动作,他只有一个方法evaluate(),用来执行。

调用流程

1. 传入测试类

传入测试类调用JUnitCoure#runClasses(Class... classes)方法。

2. 创建Request对象

之后调用到run(Computer computer, Class... classes)方法,传入的是直接new的Computer对象。在该方法中通过Request.classes(Computer computer, Class... classes)方法创建并返回了一个Request对象,
其代码如下:

    public static Request classes(Computer computer, Class... classes) {
        try {
            AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
            Runner suite = computer.getSuite(builder, classes);
            return runner(suite);
        } catch (InitializationError e) {
            return runner(new ErrorReportingRunner(e, classes));
        }
    }

    public static Request runner(final Runner runner) {
        return new Request() {
            @Override
            public Runner getRunner() {
                return runner;
            }
        };
    }

可以看出返回的request内部的runner是通过computer的getsuite方法得出的,
再看Computer#getSuite(final RunnerBuilder builder, Class[] classes)方法:

    public Runner getSuite(final RunnerBuilder builder,
            Class[] classes) throws InitializationError {
        return new Suite(new RunnerBuilder() {
            @Override
            public Runner runnerForClass(Class testClass) throws Throwable {
                //这里的builder参数实际上是AllDefaultPossibilitiesBuilder
                return getRunner(builder, testClass);
            }
        }, classes);
    }


    protected Runner getRunner(RunnerBuilder builder, Class testClass) throws Throwable {
        return builder.runnerForClass(testClass);
    }

通过上面几个方法的调用可以得出,返回的runner依赖着AllDefaultPossibilitiesBuilder。

3. 获取真正的Runner

调用run(Runner runner)方法其中的runner参数是通过Request的getRunner()方法得来的。由2中的代码可知,这里得出的runner是从Computer#getSuite方法中得出的,该方法最终调用到的是传入的AllDefaultPossibilitiesBuilder,通过他的runnerForClass方法返回出真正的runner,代码如下:

@Override
    public Runner runnerForClass(Class testClass) throws Throwable {
        List builders = Arrays.asList(
                ignoredBuilder(),
                annotatedBuilder(),
                suiteMethodBuilder(),
                junit3Builder(),
                junit4Builder());
        //通过for循环过滤出真正的runner
        for (RunnerBuilder each : builders) {
            Runner runner = each.safeRunnerForClass(testClass);
            if (runner != null) {
                return runner;
            }
        }
        return null;
    }

一般情况下如果方法只被@Test标记,将返回一个JUnit4Builder,其类代码如下:

public class JUnit4Builder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class testClass) throws Throwable {
        return new BlockJUnit4ClassRunner(testClass);
    }
}

在这里我们知道了最终得到的runnerBlockJUnit4ClassRunner的一个实例,并向它传入了我们的测试类。

4. 方法真正被run的入口

回到JUnitCore类,跟着代码走,下一步调用run(Runner runner)方法,接下来是关键,到了测试方法被真正执行的地方:

    public Result run(Runner runner) {
        Result result = new Result();
        RunListener listener = result.createListener();
        notifier.addFirstListener(listener);
        try {
            notifier.fireTestRunStarted(runner.getDescription());
            runner.run(notifier);
            notifier.fireTestRunFinished(result);
        } finally {
            removeListener(listener);
        }
        return result;
    }

我们先看执行,把RunListenerResult放一边,在方法内部调用到了Runnerrun(notifier)方法,到这里我们知道,真正的RunnerBlockJUnit4ClassRunner, 它的run方法在其父类ParentRunner中,代码如下:

    @Override
    public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        testNotifier.fireTestSuiteStarted();
        try {
            Statement statement = classBlock(notifier);
            statement.evaluate(); // 这里这行的时候是执行了每个方法的evaluate,从而使测试方法都被执行。
        } catch (AssumptionViolatedException e) {
            testNotifier.addFailedAssumption(e);
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            testNotifier.addFailure(e);
        } finally {
            testNotifier.fireTestSuiteFinished();
        }
    }

刨除其他干扰,这段代码有两句最为关键Statement statement = classBlock(notifier); statement.evaluate();classBlock方法返回了包含执行内容的Statement,然后通过evaluate方法得到了执行,也就是我们的测试类被执行了。接下来我们来分析这两行代码。

5. 获取所有需要被执行的测试方法

classBlock方法如下:

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

这里获取到了处理所有测试方法的对象,还根据情况写入了Before,After,Rule条件。我们再看他是如何获取到一系列的测试方法的:

    protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                runChildren(notifier);
            }
        };
    }

我们可以看到最终返回的Statement是一个可执行runChildren方法的Statement,所以最终的执行也就是执行runChildren方法,

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

这里有一个getFilteredChildren方法,在其方法内部调用到了getChildren方法,代码如下:

    @Override
    protected List getChildren() {
        return computeTestMethods();
    }

    protected List computeTestMethods() {
        return getTestClass().getAnnotatedMethods(Test.class);
    }

在这里终于看到了我们的注解类Test被用到了,返回了包含@Test注解方法的包装类:FrameworkMethod。接下来的runChild方法即是执行这个FrameworkMethod

6. 方法最终被执行

分析runChild方法

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

可以看到这里仍然是用的Statement来代表要被执行的对象,这里有两个关键方法methodBlock用来构建出具体的statement和真正执行statement的runLeaf方法.
首先看methodBlock的调用流程:

    protected Statement methodBlock(final FrameworkMethod method) {
        Object test;
        ·····省略部分无关代码·····
        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;
    }

    protected Statement methodInvoker(FrameworkMethod method, Object test) {
        return new InvokeMethod(method, test);
    }

public class InvokeMethod extends Statement {
    private final FrameworkMethod testMethod;
    private final Object target;

    public InvokeMethod(FrameworkMethod testMethod, Object target) {
        this.testMethod = testMethod;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        testMethod.invokeExplosively(target);
    }}

    public Object invokeExplosively(final Object target, final Object... params)
            throws Throwable {
        return new ReflectiveCallable() {
            @Override
            protected Object runReflectiveCall() throws Throwable {
                return method.invoke(target, params);
            }
        }.run();
    }

可以看到最终返回的Statement中执行方法终于调用到了method.invoke(Object obj, Object... args),没错这里终于回到了Java中的反射,就是在这里最终执行了目标方法。好了,离最后的成功只差几小步了,接下来就是发出调用执行了,我们看runLeaf方法:

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

好,在runLeaf中传入了statement, 这个statement在methodBlock方法中被构造完成,我们知道他的evalute方法是由InvokeMethod类来重写的,并最终通过反射执行了需要被测试的方法,所以执行从此开始。

7. 执行流程回顾

所以最终的执行终于清晰了:在run方法中获取到了被执行的Statement,并执行它,这个statement执行的是ParentRunner#runChildren方法,runChildren循环为每个方法调用BlockJUnit4ClassRunner#runChild,在runChild内部继续为方法创建了一个Statement,并在InvokeMethod类中重写statement的evalute方法然后传入ParentRunner#runLeaf方法最终得到了执行。嗯,到此为止!

分析总结

我们在这里只是简单的分析了通过JUnitCore类来执行测试类的调用流程,虽然最后都是通过反射来进行的调用,但是JUnit4框架为此创建了大量的类,因为JUnit4有大量的扩展功能,不只是一个简单的@Test注解而已。其实分析完执行流程才只是刚刚开始,更有价值的地方在于体会JUnit4整个框架的架构和其中运用到的设计模式,不只停留在知道作者创建了哪些类,还要站在设计的角度考虑作者为什么要这样写,比如为什么要创建Computer,RunnerBuilder,Runeer,Statement这么多类来执行最终的反射调用,这样做有什么好处,因此还需要我们进一步深入探索,学习。

你可能感兴趣的:(从执行流程分析Junit4源码)