Junit的工程结构
从上图可以清楚的看出Junit大致分为几个版块,接下来一一简略介绍这些版块的作用。
runner:定义了Junit模型中的许多基本概念,只要是一些虚类和接口,是整个Junit工程的基石
runners:提供了从注解中使用反射完成测试用例执行的实现
interval:提供了在Runner中许多虚类的默认实现,包括各类RunnerBuilder如基于注解提供Builder、各类Matchers、各类Request如类测试Request、方法测试Request。
rules:用于用户自定义扩展,规定对于不同statement的用户自定义行为
matchers:主要用于AssertThat方法,在4.4之后废弃
validator:从类、方法和域多个方面检查测试样例
experimental:一些测试特性
Runner中的基本概念
Runner:以Notifier为参数允许测试样例,运行过程中的Notifier负责监视测试过程
Request:负责记录测试样例的Description信息,同事负责提供对应的Runner。可以通过Computer结合指定或默认的RunnerBuilder来直接为一系列测试类统一提供Runner
Description:描述测试样例,使用Composite模式,组合多个样例
Result:记录异常和失败,内置一个Listener来实现与测试过程的同步,测试完成时count自增,有样例失败则加入Failure列表
Failure:将断言失败和抛出异常综合在同一个框架下,同时提供了Description的信息
Listener:监视测试过程,典型的观察者模式
Notifier:管理一系列Listener,保证线程安全
Filter:指定条件,只运行符合条件的测试样例,可以动态添加,为每次测试增加了灵活性
Runner中的依赖关系
JunitCore负责提供给用户统一的交互,从命令行运行测试样例。Notifier是一个虚类,子类需要实现如何通知Listener的方法,负责管理Listener集合,内部内置了一个静态的SafeNotifier,该类提供了一个run方法,来简单依次通知所有Listener,它用来实现在测试开始和失败出现的时候通知所有Listener。
Description是对测试样例的建模,用来组合多个测试样例,是Runner中的核心内容。
Filter也是一个虚类,子类应该实现shouldRun方法来决定对于Description是否运行。同时依然实现一个静态方法来提供什么都不过滤,以及一个判断原子描述是否等于期望描述的过滤器,对于非原子描述若其子描述均不等于期望描述则滤掉。如下列代码所示:
/** * Returns a {@code Filter} that only runs the single method described by * {@code desiredDescription} */ public static Filter matchMethodDescription(final Description desiredDescription) { return new Filter() { @Override public boolean shouldRun(Description description) { if (description.isTest()) { return desiredDescription.equals(description); } // explicitly check if any children want to run for (Description each : description.getChildren()) { if (shouldRun(each)) { return true; } } return false; } @Override public String describe() { return String.format("Method %s", desiredDescription.getDisplayName()); } }; }
Failure组合了Description和Throwable,为运行时异常和断言错误屏蔽了不一致的方面,可以向上提供错误信息和样例信息。
Request负责提供具体的Runner来run对应的测试样例,同时是Filter作用的主体,对于Filter,会返回一个新的FilterRequest,代码如下:
package org.junit.internal.requests;import org.junit.internal.runners.ErrorReportingRunner;import org.junit.runner.Request;import org.junit.runner.Runner;import org.junit.runner.manipulation.Filter;import org.junit.runner.manipulation.NoTestsRemainException;/** * A filtered {@link Request}. */public final class FilterRequest extends Request { private final Request request; /* * We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses * reflection to access this field. See * https://github.com/junit-team/junit/issues/960 */ private final Filter fFilter; /** * Creates a filtered Request * * @param request a {@link Request} describing your Tests * @param filter {@link Filter} to apply to the Tests described in * request
*/ public FilterRequest(Request request, Filter filter) { this.request = request; this.fFilter = filter; } @Override public Runner getRunner() { try { Runner runner = request.getRunner(); fFilter.apply(runner); return runner; } catch (NoTestsRemainException e) { return new ErrorReportingRunner(Filter.class, new Exception(String .format("No tests found matching %s from %s", fFilter .describe(), request.toString()))); } }}
Runner中的JunitCore
JUnitCore使用外观模式(facade),对外提供一致的界面,同时支持运行JUnit 4或JUnit 3.8.x用例,通过命令行执行用例.首先它使用jUnitCommandLineParseResult解析外部参数,将默认的TextListener加入内置的Notifier。它所运行的Runner也是由jUnitCommandLineParseResult提供的,先行通过测试filter掉不需要的样例,最后调用Runner的run方法。