欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JUnit 源码 之 基础模块

程序员文章站 2022-04-19 23:09:46
...

JUnit 源码 之 基础模块

今天突发奇想 想看看JUnit的实现,于是就翻阅了JUnit的官网和GitHub Project,并初步有所了解,就此进行简单源码记录。

  • JUnitCore
  • RunnerBuilder
  • Statement
  • annotation
  • Runner

JUnitCore 运行入口

  • main方法 一般作为命令行入口
  • static runClasses(…) 一般用于手动入口(基本不这么用)Result result = JUnitCore.runClasses(AddPencilsTest.class);
  • run(…) IDE集成入口
// from JUnitCore
public Result run(Computer computer, Class<?>... classes) 
	{ return run(Request.classes(computer, classes)); }

// from Request
public static Request classes(Computer computer, Class<?>... classes) {
    // 通过Builder进行构建Runner对象(下面重点介绍)
    AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
    Runner suite = computer.getSuite(builder, classes);
    return runner(suite);
}

// from JUnitCore
public Result run(Runner runner) {
    Result result = new Result();
    RunListener listener = result.createListener();
    notifier.addFirstListener(listener);
    try {
        notifier.fireTestRunStarted(runner.getDescription());
        runner.run(notifier); // 启动单元测试,其他部分均为Listener相关内容
        notifier.fireTestRunFinished(result);
    } finally { removeListener(listener); }
    return result;
}

RunnerBuilder 测试类的包装运行类

  • Ignore[Class]Builder – 标记为@Ignore的Test类
  • AnnotatedBuilder – 标记为@RunWith的Test类
  • JUnit4Builder – 普通类
  • AllDefaultPossibilitiesBuilder – 所有RunnerBuilder的通用工厂类
// AllDefaultPossibilitiesBuilder
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
    List<RunnerBuilder> builders = Arrays.asList(
            ignoredBuilder(), 			// @Ignore
            annotatedBuilder(), 		// @RunWith
            suiteMethodBuilder(),		// method name == suite
            junit3Builder(),
            junit4Builder());			// other (no annotation Test class)

    for (RunnerBuilder each : builders) {
        Runner runner = each.safeRunnerForClass(testClass);
        if (runner != null) { return runner; }
    }
    return null;
}
// from AnnotatedBuilder
@Override
public Runner runnerForClass(Class<?> testClass) throws Exception {
    for (Class<?> currentTestClass = testClass; currentTestClass != null;
         currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
        RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
        if (annotation != null) { return buildRunner(annotation.value(), testClass); }
    }
    return null;
}

public Runner buildRunner(Class<? extends Runner> runnerClass,
        Class<?> testClass) throws Exception {
    try {
        return runnerClass.getConstructor(Class.class).newInstance(testClass);
    } catch (NoSuchMethodException e) {
        try {
            return runnerClass.getConstructor(Class.class,
                    RunnerBuilder.class).newInstance(testClass, suiteBuilder);
        } catch (NoSuchMethodException e2) {
            String simpleName = runnerClass.getSimpleName();
            throw new InitializationError(String.format(
                    CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
        }
    }
}

Statement (各测试方法的对应封装类)

  • RunAfters
  • RunBefores
  • InvokeMethod
  • FailOnTimeout
// from RunAfters
@Override
public void evaluate() throws Throwable {
    List<Throwable> errors = new ArrayList<Throwable>();
    try { next.evaluate(); } catch (Throwable e) { errors.add(e); } 
    finally {
        for (FrameworkMethod each : afters) 
        { try { each.invokeExplosively(target); } catch (Throwable e) { errors.add(e); }}
    }
    MultipleFailureException.assertEmpty(errors);
}

// from RunBefores
@Override
public void evaluate() throws Throwable {
    for (FrameworkMethod before : befores) { before.invokeExplosively(target); }
    next.evaluate();
}

// from InvokeMethod
@Override
public void evaluate() throws Throwable { testMethod.invokeExplosively(target); }
// from FailOnTimeout
@Override
public void evaluate() throws Throwable {
    CallableStatement callable = new CallableStatement();
    FutureTask<Throwable> task = new FutureTask<Throwable>(callable);
    threadGroup = new ThreadGroup("FailOnTimeoutGroup");
    Thread thread = new Thread(threadGroup, task, "Time-limited test");
    thread.setDaemon(true);
    thread.start();
    callable.awaitStarted();
    Throwable throwable = getResult(task, thread);
    if (throwable != null) {
        throw throwable;
    }
}

private Throwable getResult(FutureTask<Throwable> task, Thread thread) {
    try {
        if (timeout > 0) { return task.get(timeout, timeUnit); } 
        else { return task.get(); }
    } catch (InterruptedException e) {
        return e; // caller will re-throw; no need to call Thread.interrupt()
    } catch (ExecutionException e) {
        // test failed; have caller re-throw the exception thrown by the test
        return e.getCause();
    } catch (TimeoutException e) {
        return createTimeoutException(thread);
    }
}

private class CallableStatement implements Callable<Throwable> {
    private final CountDownLatch startLatch = new CountDownLatch(1);

    public Throwable call() throws Exception {
        try { startLatch.countDown(); originalStatement.evaluate(); } 
        catch (Exception e) { throw e; } 
        catch (Throwable e) { return e; }
        return null;
    }

    public void awaitStarted() throws InterruptedException { startLatch.await(); }
}

annotation

  • Base – 核心使用部分,不与介绍
    • BeforeClass | Before | Test | After | AfterClass | Ignore
  • Advanced
    • RunWith – 使用指定Runner来对测试用例进行测试(主要是用于 环境部署分类组装 等)
    • Suite & Parameterized 这两个为JUnit自带用于RunWith注解中的 运行器 (本人暂且这么称呼)
      • Suite 配合 SuiteClasses 一起使用,用于整合多个测试用例一起运行测试
      • Parameterized 当需要测试某功能在不同参数下的运行情况时,可以使用该 运行器 来提供参数集
  • 具体核心代码逻辑 在下篇中讲解

Runner

用于包装一个Test类,与Test Method的对应关系为 Test Method: Runner = M : 1
可以这么描述,一般都会使用ParentRunner抽象类,其中有一个属性为children,该属性中包裹着Test Method
简单铺垫一下Runner的常用实现类的继承关系,以便于下篇文章讲解

  • ParentRunner --> Suite --> Parameterized
  • ParentRunner --> BlockJUnit4ClassRunner --> BlockJUnit4ClassRunnerWithParameters

其他略过

相关标签: JUnit