初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉junit这个框架还是值得看的,里面有许多不错的设计思想在,更何况它是kent beck和erich gamma这样的大师写的。。。。。
看junit源码最大的收获就是看到这个statement的设计,它也是我看到过的所有源码中最喜欢的设计之一。junit中runner的运行过程就是statement链的运行过程,statement是对一个单元运行的封装,每个statement都只是执行它本身所表达的逻辑,而将其他逻辑交给下一个statement处理,而且基本上的statement都存在对下一个节点的引用,从而由此构成一条statement的链,从设计模式的角度上来看,这是一个职责连模式(chain of responsibility pattern)。junit中对@beforeclass、@afterclass、@before、@after、@classrule、@rule等逻辑就是通过statement来实现的。首先来看一下statement的类结构。
statement的类结构还是比较简单的,首先statement是所有类的父类,它只定义了一个抽象的evaluate()方法,由其他子类实现该方法;而且除了fail和invokemethod类,其他类都有一个对statement本身的引用。其实从实现上,每个statement也是比较简单的,这个接下来就可以看到了。每个statement都只实现它自己的逻辑,而将其他逻辑代理给另一个statement执行,这样可以在编写每个statement的时候只关注自己的逻辑,从而保持statement本身的简单,并且易于扩展,当一条statement执行完后,整个逻辑也就执行完了。不过statement这条链也不是凭空产生的,它也是要根据一定的逻辑构造起来的,关于statement链的构造在junit中由runner负责,为了保持本文的完整性,本文会首先会讲解上述几个statement的源码,同时简单回顾statement链的构造过程,最后将通过一个简单的例子,将statement的执行过程用序列图的方式表达出来,以更加清晰的表达statement的执行过程。不过本文不会详细介绍rules相关的代码,这部分的代码将会在下一节详细介绍。
这两个statement是针对junit中@beforeclass、@before的实现的,其中@beforeclass是在测试类运行时,所有测试方法运行之前运行,并且对每个测试类只运行一次,这个注解修饰的方法必须是静态的(在runner一节中有谈过它为什么要被设计成一定是要静态方法,因为在运行每个测试方法是,测试类都会从新初始化一遍,如果不是静态类,它只运行一次的话,它运行的结果无法保存下来);@before是在每个测试方法运行之前都要运行。
在statement的设计中,@beforeclass注解的方法抽象成一个statement叫runbefores,而测试类中其他要运行的测试方法的运行过程是另一个statement叫next,在runbefores中调用完所有这些方法,而将其他逻辑交给next; @before注解的方法也是一样的逻辑,它把接下来的测试方法看成是一个statement叫next,它调用完所有@before注解的方法后,将接下来的事情交给next,因而他们共享runbefores的statement,唯一不同的是@beforeclass的runbefores可以直接调用测试类中的方法,因为他们是静态的,而@before的runbefores需要传入测试类的实例。
1 public class runbefores extends statement {
2 private final statement fnext;
3 private final object ftarget;
4 private final list<frameworkmethod> fbefores;
5 public runbefores(statement next, list<frameworkmethod> befores, object target) {
6 fnext= next;
7 fbefores= befores;
8 ftarget= target;
9 }
10 @override
11 public void evaluate() throws throwable {
12 for (frameworkmethod before : fbefores)
13 before.invokeexplosively(ftarget);
14 fnext.evaluate();
15 }
16 }
从源码中可以看到,构造runbefores时传入下一个statement、所有@beforeclass或@before注解的方法以及测试类的实例,对@beforeclass来说,传入null即可。在运行evaluate()方法时,它依次调用@beforeclass或@before注解的方法,后将接下来的逻辑交给next。从这段逻辑也可以看出如果有一个@beforeclass或@before注解的方法抛异常,接下来的这些方法就都不会执行了,包括测试方法。不过此时@afterclass或@after注解的方法还会执行,这个在下一小节中即可知道。
关于runbefores的构造要,其实最重要的是要关注它的next statement是什么,对于@beforeclass对应的runbefores来说,它的next statement那些所有的测试方法运行而组成的statement,而对于@before对应的runbefores来说,它的next statement是测试方法的statement:
1 protected statement classblock(final runnotifier notifier) {
2 statement statement= childreninvoker(notifier);
3 statement= withbeforeclasses(statement);
4
5 }
6 protected statement withbeforeclasses(statement statement) {
7 list<frameworkmethod> befores= ftestclass
8 .getannotatedmethods(beforeclass.class);
9 return befores.isempty() ? statement :
10 new runbefores(statement, befores, null);
11 }
12 protected statement methodblock(frameworkmethod method) {
13
14 statement statement= methodinvoker(method, test);
15
16 statement= withbefores(method, test, statement);
17
18 }
19 protected statement withbefores(frameworkmethod method, object target,
20 statement statement) {
21 list<frameworkmethod> befores= gettestclass().getannotatedmethods(
22 before.class);
23 return befores.isempty() ? statement : new runbefores(statement,
24 befores, target);
25 }
这两个statement是针对junit中@afterclass、@after的实现的,其中@afterclass是在测试类运行时,所有测试方法结束之后运行,不管之前的方法是否有抛异常,并且对每个测试类只运行一次,这个注解修饰的方法必须是静态的;@after是在每个测试方法运行结束后都要运行的,不管测试方法是否测试失败。
在statement的设计中,@afterclass注解的方法抽象成一个statement叫afters,而测试类中之前要运行的过程是另一个statement叫next(其实这个叫before更好一些),在runafters中等所有之前的运行过程调用完后,再调用@afterclass注解的方法; @after注解的方法也是一样的逻辑,它把之前的测试方法包括@before注解的方法看成是一个statement叫next(before?),它等测试方法或@before注解的方法调用完后,调用@after注解的方法,因而他们共享runafters的statement,唯一不同的是@afterclass的runafters可以直接调用测试类中的方法,因为他们是静态的,而@after的runafters需要传入测试类的实例。
1 public class runafters extends statement {
4 private final list<frameworkmethod> fafters;
5 public runafters(statement next, list<frameworkmethod> afters, object target) {
7 fafters= afters;
12 list<throwable> errors = new arraylist<throwable>();
13 try {
14 fnext.evaluate();
15 } catch (throwable e) {
16 errors.add(e);
17 } finally {
18 for (frameworkmethod each : fafters)
19 try {
20 each.invokeexplosively(ftarget);
21 } catch (throwable e) {
22 errors.add(e);
23 }
24 }
25 multiplefailureexception.assertempty(errors);
26 }
27 }
从源码中可以看到,构造runafters时传入下一个statement、所有@afterclass或@after注解的方法以及测试类的实例,对@afterclass来说,传入null即可。在运行evaluate()方法时,它会等之前的statement执行结束后,再依次调用@afterclass或@after注解的方法。从这段逻辑也可以看出无论之前statement执行是否抛异常,@afterclass或@after注解的方法都是会被执行的,为了避免在执行@afterclass或@after注解的方法抛出的异常覆盖之前在运行@beforeclass、@before或@test注解方法抛出的异常,这里所有的异常都会触发一次testfailure的事件,这个实现可以查看runner小节的eachtestnotifier类的实现。
对于runafters的构造,可能要注意的一点是传入runafters的statement是runbefores的实例,这个其实还是好理解的,因为runafters是在传入的statement运行结束后运行,而runbefores又是要在测试方法之前运行的,因而需要将runafters放在statement链的最头端,而后是runafters,最后才是测试方法调用的statement(invokemethod)。
2
4 statement= withbeforeclasses(statement);
5
6 }
7 protected statement withafterclasses(statement statement) {
8 list<frameworkmethod> afters= ftestclass
9 .getannotatedmethods(afterclass.class);
10 return afters.isempty() ? statement :
11 new runafters(statement, afters, null);
12 }
13 protected statement methodblock(frameworkmethod method) {
14
15 statement= withbefores(method, test, statement);
16 statement= withafters(method, test, statement);
17
19 protected statement withafters(frameworkmethod method, object target,
21 list<frameworkmethod> afters= gettestclass().getannotatedmethods(
22 after.class);
23 return afters.isempty() ? statement : new runafters(statement, afters,
24 target);
25 }
之所有要把这三个statement放在一起是因为他们都是和@test注解相关的:
1 @retention(retentionpolicy.runtime)
2 @target({elementtype.method})
3 public @interface test {
4 class<? extends throwable> expected() default none.class;
5 long timeout() default 0l;
6 }
@test注解定义了两个成员:expected指定当前测试方法如果抛出指定的异常则表明测试成功;而timeout指定当前测试方法如果超出指定的时间(以毫秒为单位),则测试失败。在statement设计中,这些逻辑都抽象成了一个statement。而@test注解的方法则被认为是真正要运行的测试方法,它的执行过程也被抽象成了一个statement。
@test注解的方法抽象出的statement命名为invokemethod,它是一个非常简单的statement:
1 public class invokemethod extends statement {
2 private final frameworkmethod ftestmethod;
3 private object ftarget;
4 public invokemethod(frameworkmethod testmethod, object target) {
5 ftestmethod= testmethod;
6 ftarget= target;
7 }
8 @override
9 public void evaluate() throws throwable {
10 ftestmethod.invokeexplosively(ftarget);
11 }
使用一个方法实例和测试类的实例构造invokemethod,在运行时直接调用该方法。并且invokemethod并没有对其他statement的引用,因而它是statement链上的最后一个节点。
1 protected statement methodblock(frameworkmethod method) {
3 statement statement= methodinvoker(method, test);
4 statement= possiblyexpectingexceptions(method, test, statement);
5 statement= withpotentialtimeout(method, test, statement);
6
7 }
8 protected statement methodinvoker(frameworkmethod method, object test) {
9 return new invokemethod(method, test);
10 }
expectexception用于处理当在@test注解中定义了expected字段时,该注解所在的方法是否在运行过程中真的抛出了指定的异常,如果没有,则表明测试失败,因而它需要该测试方法对应的statement(invokemethod)的引用:
1 public class expectexception extends statement {
2 private statement fnext;
3 private final class<? extends throwable> fexpected;
4 public expectexception(statement next, class<? extends throwable> expected) {
5 fnext= next;
6 fexpected= expected;
9 public void evaluate() throws exception {
10 boolean complete = false;
11 try {
12 fnext.evaluate();
13 complete = true;
14 } catch (assumptionviolatedexception e) {
15 throw e;
16 } catch (throwable e) {
17 if (!fexpected.isassignablefrom(e.getclass())) {
18 string message= "unexpected exception, expected<"
19 + fexpected.getname() + "> but was<"
20 + e.getclass().getname() + ">";
21 throw new exception(message, e);
22 }
23 }
24 if (complete)
25 throw new assertionerror("expected exception: "
26 + fexpected.getname());
27 }
28 }
使用invokemethod实例和一个expected的throwable class实例作为参数构造expectexception,当invokemethod实例执行后,判断其抛出的异常是否为指定的异常或者该测试方法没有抛出异常,在这两种情况下,测试都会失败,因而需要它抛出异常以处理这种情况。
7 protected statement possiblyexpectingexceptions(frameworkmethod method,
8 object test, statement next) {
9 test annotation= method.getannotation(test.class);
10 return expectsexception(annotation) ? new expectexception(next,
11 getexpectedexception(annotation)) : next;
failontimeout是在@test注解中指定了timeout值时,用于控制@test注解所在方法的执行时间是否超出了timeout的值,若是,则抛出异常,表明测试失败。在junit4当前的实现中,它引用的statement实例是expectexception(如果expected字段定义了的话)或invokemethod。它通过将引用的statement实例的执行放到另一个线程中,然后通过控制线程的执行时间以控制内部引用的statement实例的执行时间,如果测试方法因内部抛出异常而没有完成,则抛出内部抛出的异常实例,否则抛出执行时间超时相关的异常。
1 public class failontimeout extends statement {
2 private final statement foriginalstatement;
3 private final long ftimeout;
4 public failontimeout(statement originalstatement, long timeout) {
5 foriginalstatement= originalstatement;
6 ftimeout= timeout;
10 statementthread thread= evaluatestatement();
11 if (!thread.ffinished)
12 throwexceptionforunfinishedthread(thread);
13 }
14 private statementthread evaluatestatement() throws interruptedexception {
15 statementthread thread= new statementthread(foriginalstatement);
16 thread.start();
17 thread.join(ftimeout);
18 thread.interrupt();
19 return thread;
20 }
21 private void throwexceptionforunfinishedthread(statementthread thread)
22 throws throwable {
23 if (thread.fexceptionthrownbyoriginalstatement != null)
24 throw thread.fexceptionthrownbyoriginalstatement;
25 else
26 throwtimeoutexception(thread);
28 private void throwtimeoutexception(statementthread thread) throws exception {
29 exception exception= new exception(string.format(
30 "test timed out after %d milliseconds", ftimeout));
31 exception.setstacktrace(thread.getstacktrace());
32 throw exception;
33 }
34 private static class statementthread extends thread {
35 private final statement fstatement;
36 private boolean ffinished= false;
37 private throwable fexceptionthrownbyoriginalstatement= null;
38 public statementthread(statement statement) {
39 fstatement= statement;
40 }
41 @override
42 public void run() {
43 try {
44 fstatement.evaluate();
45 ffinished= true;
46 } catch (interruptedexception e) {
47 //don't log the interruptedexception
48 } catch (throwable e) {
49 fexceptionthrownbyoriginalstatement= e;
50 }
51 }
52 }
53 }
failontimeout的构造过程如同上述的其他statement类似:
8 protected statement withpotentialtimeout(frameworkmethod method,
9 object test, statement next) {
10 long timeout= gettimeout(method.getannotation(test.class));
11 return timeout > 0 ? new failontimeout(next, timeout) : next;
fail这个statement是在创建测试类出错时构造的statement,这个设计思想有点类似null object模式,即保持编程模型的统一,即使在出错的时候也用一个statement去封装,这也正是runner中errorreportingrunner的设计思想。
fail这个statement很简单,它在运行时重新抛出之前记录的异常,其构造过程也是在创建测试类实例出错时构造:
2 object test;
3 try {
4 test= new reflectivecallable() {
5 @override
6 protected object runreflectivecall() throws throwable {
7 return createtest();
8 }
9 }.run();
10 } catch (throwable e) {
11 return new fail(e);
12 }
14 }
15 public class fail extends statement {
16 private final throwable ferror;
17 public fail(throwable e) {
18 ferror= e;
19 }
20 @override
21 public void evaluate() throws throwable {
22 throw ferror;
23 }
24 }
runrules这个statement是对@classrule和@rule运行的封装,它会将定义的所有rule应用到传入的statement引用后返回,由于它内部还有一些比较复杂的逻辑,关于rule将有一个单独的小节讲解。这里主要关注runrules这个statement的实现和构造:
1 public class runrules extends statement {
2 private final statement statement;
3 public runrules(statement base, iterable<testrule> rules, description description) {
4 statement= applyall(base, rules, description);
5 }
6 @override
7 public void evaluate() throws throwable {
8 statement.evaluate();
10 private static statement applyall(statement result, iterable<testrule> rules,
11 description description) {
12 for (testrule each : rules)
13 result= each.apply(result, description);
14 return result;
17 protected statement classblock(final runnotifier notifier) {
18
19 statement= withafterclasses(statement);
20 statement= withclassrules(statement);
21 return statement;
22 }
23 private statement withclassrules(statement statement) {
24 list<testrule> classrules= classrules();
25 return classrules.isempty() ? statement :
26 new runrules(statement, classrules, getdescription());
28 protected statement methodblock(frameworkmethod method) {
29
30 statement= withrules(method, test, statement);
31 return statement;
32 }
33 private statement withrules(frameworkmethod method, object target,
34 statement statement) {
35 list<testrule> testrules= gettestclass().getannotatedfieldvalues(
36 target, rule.class, testrule.class);
37 return testrules.isempty() ? statement :
38 new runrules(statement, testrules, describechild(method));
39 }
rule分为@classrule和@rule,@classrule是测试类级别的,它对一个测试类只运行一次,而@rule是测试方法级别的,它在每个测试方法运行时都会运行。runrules的构造过程中,我们可以发现runrules才是最外层的statement,testrule中要执行的逻辑要么比@beforeclass、@before注解的方法要早,要么比@afterclass、@after注解的方法要迟。
假设在一个测试类中存在多个@beforeclass、@afterclass、@before、@after注解的方法,并且有两个测试方法testmethod1()和testmethod2(),那么他们的运行序列图如下所示(这里没有考虑rule,对@classrule的runrules,它的链在最前端,而@rule的runrules则是在runafters的前面,关于rule将会在下一节中详细讲解):