从执行流程分析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<RunnerBuilder> 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<FrameworkMethod> getChildren() {
        return computeTestMethods();
    }

    protected List<FrameworkMethod> 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这么多类来执行最终的反射调用,这样做有什么好处,因此还需要我们进一步深入探索,学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • JUnit Intro Android基于JUnit Framework来书写测试代码。JUnit是基于Java语...
    chandarlee阅读 2,244评论 0 50
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 1.写在前面 基于junit 4.12版本,对junit源码阅读之后的理解和总结,如有不正确的地方,请多指正 2....
    春狗阅读 2,384评论 0 8
  • 自小,我是羡慕那些文人墨客的。笔上的功夫,炉火纯青。一个个中国汉字历经他们之手便大放异彩。这是一种本事,一种...
    远方呀阅读 124评论 0 0