阅读前提条件,了解JUnit4的基本用法。代码版本: 3637550
从执行流程来分析
一般情况下使用IDE开发项目通过鼠标很容易执行测试方法和类,分析源码的话我们就要找到程序的入口,一步一步看程序是怎么跑起来的,下面我们先看如何在不依赖IDE的情况下让测试类跑起来。
手动运行方式
程序运行入口类:JUnitCore
。该类有两种方式来执行测试类:
mian
方法:即最常见的public static void main(String... args)
,通过在命令行输入要执行测试的类作为参数来运行测试方法。通过使用
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的大部分功能,可以进行过滤和分类,处理BeforeClass
,AfterClass
还有ClassRule
,创建复合的Description
,并按照顺序来执行多个测试。不过仍需要子类来构造并真正实现执行需要被Run的对象,BlockJUnit4ClassRunner
:继承自ParentRunner
是默认的测试类的runner
。Suite
:作为一个runner
,通过它可以包含多个类来进行测试执行。RunnerBuilder
:用来给测试类创建runner。AllDefaultPossibilitiesBuilder
: 主要是在runnerForClass(Class<?> testClass)
方法中根据注解标识返回合适的RunnerBuilder
。比如,如过被测试的方法只被@Test标识,那么就会返回JUnit4Builder
。JUnit4Builder
:默认情况下被调用,创建返回BlockJUnit4ClassRunner
实例。Request
:对被运行的测试类的抽象描述。Computer
:根据RunnerBuilder
用来创建出runner
和suite
。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);
}
}
在这里我们知道了最终得到的runner
是BlockJUnit4ClassRunner
的一个实例,并向它传入了我们的测试类。
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;
}
我们先看执行,把RunListener
和Result
放一边,在方法内部调用到了Runner
的run(notifier)
方法,到这里我们知道,真正的Runner
是BlockJUnit4ClassRunner
, 它的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
这么多类来执行最终的反射调用,这样做有什么好处,因此还需要我们进一步深入探索,学习。