Junit4源码学习心得
环境为Eclipse,Junit-4.8.1。本文将跟踪Junit框架执行的流程,为大家一步一步呈现出Junit的本质。由于文章是对源码的讲解,所以阅读时最好有源码可以对照。
在Eclipse环境下,集成了Junit Test,我们执行的测试代码,就是通过这个来调用的,为了能够更好的跟踪程序的流程,我们需要绕过这个调用框架,而是直接通过自己代码中的main入口来执行测试代码了,查看源码中org.junit.ruuuer包下的JunitCore文件,这个文件包含一个main入口,查看代码注释,JunitCore是一个用来执行tests的接口,是用来在command line执行tests的,也可以在代码中调用来执行tests。如
public class JunitCoreToRun {
@Test
public void testRunFromMain(){
System.out.println("RunFromMain");
}
public static void main(String[] args){
JUnitCore.runClasses(JunitCoreToRun.class);
}
}
代码执行打印结果:RunFromMain。
为了更直观的展现代码的逻辑,从JunitCore中抽取正真执行tests的代码,编写一个自己的JunitCore
public class MyJunitCore{
@Test
public void test(){
System.out.println("MyJunitCore");
}
public static void main(String[] args) {
Request request = Request.classes(MyJunitCore.class);
RunNotifier fNotifier = new RunNotifier();
Runner runner = request.getRunner();
fNotifier.fireTestRunStarted(runner.getDescription());
runner.run(fNotifier);
}
}
代码执行打印结果:
MyJunitCore。
Request是一个抽象类,暂且把它命名为请求,它定义了一个抽象函数public abstract Runner getRunner(),以及一些静态的方法用来返回一个Request对象。
public static Request method(Class> clazz, String methodName)
这个函数用来创建一个能执行单个test的Request对象
public static Request aClass(Class> clazz)
这个函数用来创建一个能执行类中的所有tests的Request对象
public static Request classes(Class>... classes)
这个函数用来创建一个能执行一系列classes中所有tests的Request对象。
Request最重要的是抽象方法getRunner(),他返回的Runner是真正执行tests的。所有的获取Request的静态方法都几乎同时指向了一个类AllDefaultPossibilitiesBuilder,它继承自RunnerBuilder,RunnerBuilder是一个抽象类包含一个抽象方法public abstract Runner runnerForClass(Class> testClass) throws Throwable。
AllDefaultPossibilitiesBuilder重载了runnerForClass方法,且看代码
@Override
public Runner runnerForClass(Class> testClass) throws Throwable {
List builders= Arrays.asList(
ignoredBuilder(),
annotatedBuilder(),
suiteMethodBuilder(),
junit3Builder(),
junit4Builder());
for (RunnerBuilder each : builders) {
Runner runner= each.safeRunnerForClass(testClass);
if (runner != null)
return runner;
}
return null;
}
ignoredBuilder(),annotatedBuilder(),suiteMethodBuilder(),junit3Builder(),junit4Builder(),分别创建一个具体的RunnerBuilder。
可以看出,AllDefaultPossibilitiesBuilder就是设计模式中的Builder模式,根据传入的参数testClass来生成具体的RunnerBuilder对象,而RunnerBuilder对象再Builder具体的Runner对象。下面分析几个RunnerBuilder以及其对应的Runner对象。
ignoredBuilder()创建 IgnoredBuilder,继承自RunnerBuilder,它重载的runnerForClass方法
@Override
public Runner runnerForClass(Class> testClass) {
if (testClass.getAnnotation(Ignore.class) != null)
return new IgnoredClassRunner(testClass);
return null;
}
返回一个 IgnoredClassRunner,IgnoredClassRunner继承自Runner,具体的Runner在后面讲述。IgnoredClassRunner几乎什么也不干,直接跳过tests的执行。可将上面的MyJunitCore改为
@Ignore(value="Ignore MyJunitCore")
public class MyJunitCore{
@Test
public void test(){
System.out.println("MyJunitCore");
}
public static void main(String[] args) {
Request request = Request.classes(MyJunitCore.class);
RunNotifier fNotifier = new RunNotifier();
Runner runner = request.getRunner();
fNotifier.fireTestRunStarted(runner.getDescription());
runner.run(fNotifier);
}
}
那么就会为这个tests创建一个IgnoredClassRunner,tests将会被直接忽略。所有当test类被@Ignore修饰,就会生成对应的IgnoredClassRunner。
annotatedBuilder()创建AnnotatedBuilder,AnnotatedBuilder继承自RunnerBuilder,它重载的runnerForClass方法,返回由注释 @RunWith(value=xxxx.class)对应的xxxx.class Runner对象。如将MyJunitCore改为
@RunWith(value=JUnit4.class)
public class MyJunitCore{
@Test
public void test(){
System.out.println("MyJunitCore");
}
public static void main(String[] args) {
Request request = Request.classes(MyJunitCore.class);
RunNotifier fNotifier = new RunNotifier();
Runner runner = request.getRunner();
fNotifier.fireTestRunStarted(runner.getDescription());
runner.run(fNotifier);
}
}
那么就会为这个tests创建Junit4.class对应的 Junit4 Runner对象。
suiteMethodBuilder()创建一个SuiteMethodBuilder,继承自
RunnerBuilder,它重载runnerForClass方法,返回SuiteMethod Runner对象,SuiteMethod继承自JUnit38ClassRunner,这个是runner对象是为了兼容4.x以前的版本(3.x)。如将MyJunitCore改为
public class MyJunitCore extends TestCase{
public static junit.framework.Test suite(){
TestSuite suite = new TestSuite();
suite.addTestSuite(MyJunitCore.class);
return suite;
}
public void test3x(){
System.out.println("MyJunitCore True");
}
public void tset3x(){
System.out.println("MyJunitCore Wrong");
}
public static void main(String[] args) {
Request request = Request.classes(MyJunitCore.class);
RunNotifier fNotifier = new RunNotifier();
Runner runner = request.getRunner();
fNotifier.fireTestRunStarted(runner.getDescription());
runner.run(fNotifier);
}
}
注意3.x版本并不支持@Test注释,因为那个时候的java版本还没有引入注释功能,所有需要执行的测试方法名必须是已test开头,如上面的test3x,而如果不小心将方法写成了tset3x,该方法就无法执行。
因此上述程序的执行控制行结果为:MyJunitCore True。
同时所有的测试类都必须继承TestCase方法。可以看出,4.x版本相对于3.x版本是有很大的改进的。
后面的RunnerBuilder就不一一介绍,重点介绍下 JUnit4Builder,它继承自RunnerBuilder,重载runnerForClass方法返回一个BlockJUnit4ClassRunner。BlockJUnit4ClassRunner就是接下来重点分析的内容。
BlockJUnit4ClassRunner继承自ParentRunner,先来分析ParentRunner:
ParentRunner包含几个重要的成员变量TestClass fTestClass,Filter fFilter,Sorter fSorter
TestClass是一个包装类,其包含一个Class> fClass,fClass就是我们要运行的tests类。TestClass用来筛选并获取fClass中符合条件的方法和域,筛选的方法就是通过方法或者域的注释(即Annotations)。有了TestClass,就能方便的获取到tests类所有包含注释的方法以及域,也为后续获取如包含@Test、@BeforeClass、@AfterClass、@Before、@After的方法提供了接口。
同时 ParentRunner
继承自 Runner,Runner定义了两个抽象方法:
public abstract Description getDescription();
public abstract void run(RunNotifier notifier);
run方法就是正在执行tests的入口,且看ParentRunner如何实现Runner:
@Override
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier= new EachTestNotifier(notifier,
getDescription());
try {
Statement statement= classBlock(notifier);
statement.evaluate();//开始执行测试
} catch (AssumptionViolatedException e) {
testNotifier.fireTestIgnored();
} catch (StoppedByUserException e) {
throw e;
} catch (Throwable e) {
testNotifier.addFailure(e);
}
}
Statement statement= classBlock(notifier);
statement.evaluate();//开始执行测试
这个Statement是何方神圣:
public abstract class Statement {
/**
* Run the action, throwing a {@code Throwable} if anything goes wrong.
*/
public abstract void evaluate() throws Throwable;
}
Statement是一个抽象类,有一个抽象方法。此抽象类采用了设计模式中的装饰模式,有兴趣的可以去看一下设计模式中的装饰模式(Decorator)。
且看函数classblock的实现:
protected Statement classBlock(final RunNotifier notifier) {
Statement statement= childrenInvoker(notifier);
statement= withBeforeClasses(statement);
statement= withAfterClasses(statement);
return statement;
}
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
runChildren(notifier)最终执行的是抽象的方法:
protected abstract List getChildren();
getChildren()方法获取要执行的列表,然后依次对列表中的T对象执行下面的抽象方法runChild(T child,RunNotifier notifier):
protected abstract void runChild(T child, RunNotifier notifier);
在这里,模版类T可能是两种情况,一种是FrameworkMethod,另一种是Runner。
前面讨论的ParentRunner类,如果对设计模式中的组合(Composite)模式有以一定的了解,可能理解起来就简单许多。
对于BlockJUnit4ClassRunner来说,这个T对象是 FrameworkMethod(FrameworkMethod是对被测试的类中要被测试的方法的包装,能够更方便的执行被测试类的中的指定方法),runChildren()的意思就是执行BlockJUnit4ClassRunner通过重载getChildren返回的所有符合条件的FrameworkMethod,也就是执行所有测试类中被@Test标记的方法。
对于Suite来说( public class Suite extends ParentRunner ),这个T对象是一个Runner,那么runChildren的意思就是执行Suite通过重载getChildren返回的所有Runner。如:
public class ClassA{
@Test
public void test(){
System.out.println("Class A");
}
}
public class ClassB{
@Test
public void test(){
System.out.println("Class B");
}
}
@RunWith(Suite.class)
@SuiteClasses({ClassA.class,ClassB.class})
public class MySuite {
}
public class TestMySuite{
public static void main(String[] args) {
Request request = Request.classes(MySuite.class);
RunNotifier fNotifier = new RunNotifier();
Runner runner = request.getRunner(); //生成的runner对象是一个Suite的实例,Suite对象中的List
//fRunners包含两个BlockJUnit4ClassRunner实例
fNotifier.fireTestRunStarted(runner.getDescription());
runner.run(fNotifier);
}
}
对于上面的例子,ClassA对应一个测试对象BlockJUnit4ClassRunner(假设名为RunnerA),ClassB会对应一个BlockJUnit4ClassRunner(假设名为RunnerB) ,MySuite会对应一个测试对象Suite(假设名为RunnerSuite),当RunnerSuite执行run方法时,会分别执行RunnerA和RunnerB的run方法,RunnerA的run方法会执行ClassA中被@Test、@BeforeClass、@AfterClass、@Before、@After修饰的方法,RunnerB的run方法会执行ClassB中被@Test、@BeforeClass、@AfterClass、@Before、@After修饰的方法。
ParentRunner重载的run方法,最终通过Statement的evaluate()方法来执行这些操作,而测试类中所有被@Test、@BeforeClass、@AfterClass、@Before、@After修饰的方法都被封装在了statement对象中。具体见下面带注释
protected Statement classBlock(final RunNotifier notifier) {
Statement statement= childrenInvoker(notifier);//获取一个statement,其evaluate方法调用runChildren,runChildren前面已经有部分介绍,下面会具体讲解。
statement= withBeforeClasses(statement);//装饰上@BeforeClass修饰的方法
statement= withAfterClasses(statement); //装饰上@AfterClass修饰的方法
return statement;//返回经过各种装饰的statement,执行这个装饰的evaluate方法,就能运测试类了。
}
对于childrenInvoker调用的runChildren,其行为是执行抽象方法protected abstract List getChildren()获取List,然后对List中的每一个T元素进行protected abstract void runChild(T child, RunNotifier notifier)操作,这两个方法都是抽象方法。
BlockJUnit4ClassRunner对上述抽象方法的实现:
List getChildren()方法返回测试类中所有被@Test注解的方法,(FrameworkMethod类是对测试类中方法的简单封装,这个前面已经讲过,可以直接把它看作是测试类中的方法)
下面看重载的runChildren()方法:
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
EachTestNotifier eachNotifier= makeNotifier(method, notifier);
if (method.getAnnotation(Ignore.class) != null) {//如果方法被@Ignore修饰,直接跳过这个方法,@Ignore修饰符可以修饰方法和类,当修饰的是类时,整个测试类都会被忽略,这里修饰的具体的方法。
eachNotifier.fireTestIgnored();
return;
}
eachNotifier.fireTestStarted();//通知测试开始
try {
methodBlock(method).evaluate(); //生成一个Statement并执行,具体见下面的methodBlock方法。
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (Throwable e) {
eachNotifier.addFailure(e);
} finally {
eachNotifier.fireTestFinished();//通知测试结束
}
}
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对象,可以理解为起点
statement= possiblyExpectingExceptions(method, test, statement);//装饰一个异常处理,如果@Test注解中expected()设置了期望的异常,就是进行装饰,注意
//这里返回的statement如果在条件成立进行装饰的情况下,返回的statement已经不是传入的
//statement了。
statement= withPotentialTimeout(method, test, statement); //装饰一个超时处理,如果@Test注解中timeout的值设置为 > 0,会装饰进行装饰
statement= withBefores(method, test, statement); //装饰 测试类中的@Before方法
statement= withAfters(method, test, statement); //装饰 测试类中的@After方法
statement= withRules(method, test, statement); //装饰 规则方法,为用户自定义规则提供了接口,后续版本可能会代替前面的4种装饰方法
return statement; //返回经过层层装饰的statement
}
好了,整个执行的流程分析的差不多了,还有些没分析到的如Description,RunListener,@Rule、MethodRule 等在后续会给出。
第一次发帖,请多多指教。