junit单元测试属于是人都会用的东西,面试被问的不多,但是如何你能聊点高级点的知识,肯定会让面试官刮目相看。
比如下面几个问题,不是每个人都可以脱口而出。
1 junit测试函数可以带参数吗
2 单元测试架构用了什么设计模式
3 指定RunWith参数的作用是什么
4 @After和@Before标签的实现原理
junit里测试由runner开始,不同runner满足不同的测试需求,比如SuitRunner可以同时执行多个测试,而默认的runner只能每次执行一个测试。
通过debug我们可以跟到ClassRequest类:
ScreenClip.png
这里会返回一个匹配的runner,主要逻辑在AllDefaultPossibilitiesBuilder里。
@Override
public Runner getRunner() {
if (runner == null) {
synchronized (runnerLock) {
if (runner == null) {
runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass);
}
}
}
return runner;
}
AllDefaultPossibilitiesBuilder的runnerForClass会去从预设的RunnerBuilder匹配,只要找到一个合适的runner就会返回。不难猜到,annotatedBuilder正是用于处理@RunWith标签指定了runner的情况,如果没有匹配的runner,会用JUnit4Builder提供的BlockJUnit4ClassRunner。
@Override
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;
}
annotatedBuilder的runnerForClass方法实现
@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 class JUnit4Builder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
return new BlockJUnit4ClassRunner(testClass);
}
}
继续debug到了JUnitCore,从作者注释可知,JUnitCore是测试门面类,它包装了从不同入口进行测试的方法,同时实现了测试的主框架。
/**
* <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests,
* JUnit 3.8.x tests, and mixtures. To run tests from the command line, run
* <code>java org.junit.runner.JUnitCore TestClass1 TestClass2 ...</code>.
* For one-shot test runs, use the static method {@link #runClasses(Class[])}.
* If you want to add special listeners,
* create an instance of {@link org.junit.runner.JUnitCore} first and use it to run the tests.
*
这个函数是给命令行调用的。
public static void main(String... args) {
Result result = new JUnitCore().runMain(new RealSystem(), args);
System.exit(result.wasSuccessful() ? 0 : 1);
}
不管哪个入口,最终都会走到runner,这段代码可以看出junit的设计采用了观察者模式,junit的listener负责接受测试执行阶段的各种事件,并执行自己的处理逻辑,listener注册给notifer,notifer负责在特定阶段通知所有的listener。
public Result run(Runner runner) {
Result result = new Result();
//ResultListener用于收集测试结果
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
//会调用result的testRunStarted
notifier.fireTestRunStarted(runner.getDescription());
//执行测试
runner.run(notifier);
//会调用result的testRunFinished
notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
查看下Result类定义可知,这个类是用来收集测试结果,比如打印出耗时,多少成功失败等。
public class Result implements Serializable {
/** Only set during deserialization process. */
private SerializedForm serializedForm;
public Result() {
count = new AtomicInteger();
ignoreCount = new AtomicInteger();
failures = new CopyOnWriteArrayList<Failure>();
runTime = new AtomicLong();
startTime = new AtomicLong();
}
往下翻可以发现result内置的listener,用于接收测试开始,完成等事件的回调,这里在testRunStarted的时候保存starttime,在testRunFinished记录了endTime 。
@RunListener.ThreadSafe
private class Listener extends RunListener {
@Override
public void testRunStarted(Description description) throws Exception {
startTime.set(System.currentTimeMillis());
}
@Override
public void testRunFinished(Result result) throws Exception {
long endTime = System.currentTimeMillis();
runTime.addAndGet(endTime - startTime.get());
}
@Override
public void testFinished(Description description) throws Exception {
count.getAndIncrement();
}
调到resultListener里记录开始测试的时间
@RunListener.ThreadSafe
private class Listener extends RunListener {
@Override
public void testRunStarted(Description description) throws Exception {
startTime.set(System.currentTimeMillis());
}
测试完成后,记录测试完成的时间
@RunListener.ThreadSafe
@Override
public void testRunFinished(Result result) throws Exception {
long endTime = System.currentTimeMillis();
runTime.addAndGet(endTime - startTime.get());
}
methodBlock函数负责把@Before和@After组装到statement上,有点像JDK动态代理的链式简化版,如果同时有after和before,那么会先调用beofore,再执行测试方法,最后调用after代码,类似advise,这是由不同Statement代码实现的。
protected Statement methodBlock(FrameworkMethod method) {
Object test;
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest();
}
}.run();
} catch (Throwable e) {
return new Fail(e);
}
Statement statement = methodInvoker(method, test);
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement);
//获取before注解的方法
statement = withBefores(method, test, statement);
//获取after注解的方法
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
return statement;
}
protected Statement withBefores(FrameworkMethod method, Object target,
Statement statement) {
//获取有@before注解的方法
List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(
Before.class);
return befores.isEmpty() ? statement : new RunBefores(statement,
befores, target);
}
RunAfter实现逻辑,RunAfters会把before复制给自己的next属性,先调用next,而RunBefore则会先调用自己的方法,再调用测试方法。
public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
this.next = next;
this.befores = befores;
this.target = target;
}
@Override
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) {
before.invokeExplosively(target);
}
next.evaluate();
}
public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
this.next = next;
this.afters = afters;
this.target = target;
}
@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);
}
后续会继续分析junit和springboot的初始化关系,比如junit如何加载spring环境。
网友评论