Junit4源码学习笔记

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 等在后续会给出。
第一次发帖,请多多指教。






你可能感兴趣的:(Junit4源码学习笔记)