一直以来都很好奇Junit是怎么让代码跑起来的,是按照常规思路:先创建对象,然后再调用其方法吗?趁着这个节日现在刚好分析记录一下。

示例代码

先下一个简单的单元测试类:

package com.xx.lib;
import org.junit.Test;

public class JunitDemo {

    //  测试demo代码
    @Test
    public void test01() {
        System.out.println("000");
    }
}

执行结果:

junit5 testng 活跃度对比 junit运行_Core

junit执行入口代码定位

如果要分析代码的执行入口,我们可以在需要执行的方法里加一个断点,然后通过debug的方式执行查看其调用栈信息;
效果如下:

junit5 testng 活跃度对比 junit运行_junit5 testng 活跃度对比_02

由于是通过idea执行的单元测试,该单元测试的启动会由idea的插件执行(即JunitStarter)

junit5 testng 活跃度对比 junit运行_1024程序员节_03


真正Junit框架的入口是:JunitCore这个类

JunitCore

首先看看类结构:

junit5 testng 活跃度对比 junit运行_Computer_04

初步看:这个类竟然有一个main方法,各种重载的run方法,感觉有戏;

直接给出结论

测试用例是使用JUnitCore 类来执行的,JUnitCore 是运行测试的外观类。
要从命令行运行测试,可以运行java org.junit.runner.JUnitCore。
对于只有一次的测试运行,可以使用静态方法 runClasses(Class[])。

runClasses方法

我们先来使用一下runClasses这个方法:

org.junit.runner.JUnitCore#runClasses(java.lang.Class<?>...)

我们编写一个类来验证一下,代码如下:

package com.xx.lib;
import org.junit.Test;
import org.junit.runner.JUnitCore;

public class MyTest {

    public static void main(String[] args) {
        JUnitCore.runClasses(MyTest.class);
    }

    @Test
    public void test01() {
        System.out.println("666");
    }
}

执行结果如下,成功第执行了我们标有@Test注解的测试方法:

junit5 testng 活跃度对比 junit运行_Test_05

接下来我们来分析一下runClasses这个方法执行流程:

public static Result runClasses(Class<?>... classes) {
        return runClasses(defaultComputer(), classes);
    }

defaultComputer()方法返回了一个 org.junit.runner.Computer 对象

public static Result runClasses(Computer computer, Class<?>... classes) {
        return new JUnitCore().run(computer, classes);
    }

JUnitCore就是当前类,我们继续跟踪方法

public Result run(Computer computer, Class<?>... classes) {
        return run(Request.classes(computer, classes));
    }

org.junit.runner.Request#classes(org.junit.runner.Computer, java.lang.Class<?>…)是静态方法,该方法返回的是一个 org.junit.runner.Request 对象;
该方法里会生成一个org.junit.runner.Runner对象,并会与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) {
            throw new RuntimeException(
                    "Bug in saff's brain: Suite constructor, called as above, should always complete");
        }
    }

使用了AllDefaultPossibilitiesBuilder 对象,生成一个Runner 对象并与Request 进行绑定;即runner方法,代码如下:

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

创建一个Request对象,并重写getRunner方法;

public Result run(Request request) {
        return run(request.getRunner());
    }

代码回到JUnitCore中,继续执行run方法(参数就是上一步绑定的runner,即computer.getSuite(builder, classes)的返回值)


最终调用的方法是:

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

notifier是一个 org.junit.runner.notification.RunNotifier 实例,
runner是上面说的computer.getSuite(builder, classes)方法生成的对象,我们来分析一下:
1、computer是 org.junit.runner.Computer对象
2、builder是 org.junit.internal.builders.AllDefaultPossibilitiesBuilder对象

通过进一步源码分析:
runner是 org.junit.internal.builders.AllDefaultPossibilitiesBuilder#runnerForClass得到的返回对象:

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