看完类结构图,那么我们再来看一下源码吧。由于description在junit中应用广泛,又是相对独立于功能的,因而将从description开始:
如上文所说,description是junit中对runner和测试方法的描述,它包含三个字段:
1 private final arraylist<description> fchildren;
2 private final string fdisplayname;
3 private final annotation[] fannotations;
displayname对测试方法的格式为:(),如:
testfailed(levin.blog.junit.sample.simple.corejunit4sampletest)
对runner来说,displayname一般为runner所封装的测试类,然而对没有根类的suite,该值为”null”。annotations字段为测试方法或测试类上所具有的所有注解类。children对测试方法来说为空,对runner来说,表达runner内部所有的测试方法的description或runner的description。作为junit的用户,除非自定义runner,其他的,我们一般都是通过注册自己的runlistener来实现自己想要的统计和信息提示工作,而在listener中并没有直接暴露给我们runner或者是测试类的实例,它是通过提供description实例的方式来获取我们需要的信息。description提供以下的接口供我们使用:
1 public string getdisplayname();
2 public arraylist<description> getchildren();
3 public boolean issuite();
4 public boolean istest();
5 public int testcount();
6 public <t extends annotation> t getannotation(class<t> annotationtype);
7 public collection<annotation> getannotations();
8 public class gettestclass();
9 public string getclassname();
10 public string getmethodname();
runner实现了disacribable接口,该接口只包含一个方法:
1 public interface describable {
2 public abstract description getdescription();
3 }
该方法在parentrunner中创建一个description,并遍历当前runner下的children,并将这些child的description添加到description中最后返回:
1 @override
2 public description getdescription() {
3 description description= description.createsuitedescription(
4 getname(), getrunnerannotations());
5 for (t child : getfilteredchildren())
6 description.addchild(describechild(child));
7 return description;
8 }
9 blockjunit4classrunner:
10 @override
11 protected description describechild(frameworkmethod method) {
12 return description.createtestdescription(
13 gettestclass().getjavaclass(),
14 testname(method), method.getannotations());
15 }
16 suite:
17 @override
18 protected description describechild(runner child) {
19 return child.getdescription();
20 }
21
runnotifier类和runlistener类对测试方法运行事件的发布
runlistener是junit提供的自定义对测试运行方法统计的接口,junit用户可以继承runlistener类,在junit运行开始、结束以及每一个测试方法的运行开始、结束、测试失败以及假设出错等情况下加入一些自己的逻辑,如统计整个junit运行的时间(这个在result中已经实现了)、每个运行方法的时间、运行最后有多少方法成功,多少失败等,如上例中的logrunlistener。runlistener定义了如下接口:
1 public class runlistener {
2 public void testrunstarted(description description) throws exception {
4 }
5 public void testrunfinished(result result) throws exception {
6 }
7 public void teststarted(description description) throws exception {
8 }
9 public void testfinished(description description) throws exception {
11 }
12 public void testfailure(failure failure) throws exception {
13 }
14 public void testassumptionfailure(failure failure) {
15 }
16 public void testignored(description description) throws exception {
17 }
18 }
这个类需要注意的是:1. testfinished()不管测试方法是成功还是失败,这个方法总是会被调用;2. 在测试方法中跑出assumptionviolatedexception并不认为是测试失败,一般在测试方法中调用assume类中的方法而失败,会跑该异常,在junit的默认实现中,对这些方法只是简单的忽略,并发布testassumptionfailure()事件,并不认为该方法测试失败,自定义的runner可以改变这个行为;3. 当我们需要自己操作runner实例是,result的信息需要自己手动的注册result中定义的listener,不然result中的信息并不会填写正确,如上例中的做法:
1 result result = new result();
2 notifier.addfirstlistener(result.createlistener());
在实现时,所有事件响应函数提供给我们有三种信息:description、result和failure。其中description已经在上一小节中介绍过了它所具有的信息,这里不再重复。对于result,前段提到过只有它提供的listener后才会取到正确的信息,它包含的信息有:总共执行的测试方法数、忽略的测试方法数、以及所有在测试过程中抛出的异常列表、整个测试过程的执行时间等,result实例在junit运行结束时传入testrunfinished()事件方法中,以帮助我们做一些测试方法执行结果的统计信息,事实上,我感觉这些信息很多时候还是不够的,需要我们在自己的runlistener中自己做一些信息记录。failure类则记录了测试方法在测试失败时抛出的异常以及该测试方法对应的description实例,当在构建runner过程中出现异常,failure也会用于描述测试类,如上例中,如果beforeclass()方法不是静态的话,在初始化runner时就会出错,此时的运行结果如下:
==>junit4 started with description:
levin.blog.junit.sample.simple.corejunit4sampletest
==>test method started with description: initializationerror(levin.blog.junit.sample.simple.corejunit4sampletest)
==>test method failed with failure: initializationerror(levin.blog.junit.sample.simple.corejunit4sampletest): method beforeclass() should be static
==>test method finished with description: initializationerror(levin.blog.junit.sample.simple.corejunit4sampletest)
==>junit4 finished with result:
failurecount: 1
ignorecount: 0
runcount: 1
runtime: 3
failures: [initializationerror(levin.blog.junit.sample.simple.corejunit4sampletest): method beforeclass() should be static]
runner中的run()方法需要传入runnotifier实例,它是对runlistener的封装,我们可以向其注册多个runlistener,当runner需要发布某个事件时,就会通过它来代理,它则会遍历所有已注册的runlistener,并运行相应的事件。当运行某个runlistener抛异常时,它会首先将这个runlistener移除,并发布测试失败事件,在该事件中的description为一个名为“test mechanism”的description,因为此时是注册的事件处理器失败,无法获知一个description实例:
1 private abstract class safenotifier {
2 void run() {
3 synchronized (flisteners) {
4 for (iterator<runlistener> all= flisteners.iterator();
5 all.hasnext();)
6 try {
7 notifylistener(all.next());
8 } catch (exception e) {
9 all.remove(); // remove the offending listener first to avoid an infinite loop
11 firetestfailure(new failure(
12 description.test_mechanism, e));
13 }
14 }
15 }
16 abstract protected void notifylistener(runlistener each) throws exception;
18 }
在runnotifier类中还有一个pleasestop()方法从而可以在测试中途停止整个测试过程,其实现时在发布teststarted事件时,如果发现pleasestop字段已经为true,则抛出stoppedbyuserexception,当runner接收到该异常后,将该异常直接抛出。
事实上,parentrunner在发布事件时,并不直接和runnotifier打交道,而是会用eachtestnotifier类对其进行封装,该类只是对multiplefailureexception做了处理,其他只是一个简单的代理。在遇到multiplefailureexception,它会遍历内部每个exception,并对每个exception发布testfailure事件。当在运行@after注解的方法时抛出多个异常类(比如测试方法已经抛出异常了,@after注解方法中又抛出异常或者存在多个@after注解方法,并多个@after注解方法抛出了多个异常),此时就会构造一个multiplefailureexception,这种设计可能是出于对防止测试方法中的exception被@after注解方法中的exception覆盖的问题引入的。
frameworkmethod与frameworkfield类
frameworkmethod是对java反射中method的封装,它提供了对方法的验证、调用以及处理子类方法隐藏父类方法问题,其主要提供的接口如下:
1 public method getmethod();
2 public object invokeexplosively(final object target, final object params);
3 public string getname();
4 public void validatepublicvoidnoarg(boolean isstatic, list<throwable> errors);
5 public void validatepublicvoid(boolean isstatic, list<throwable> errors)
6 public void validatenotypeparametersonargs(list<throwable> errors);
7 @override
8 public boolean isshadowedby(frameworkmethod other);
9 @override
10 public annotation[] getannotations();
11 public <t extends annotation> t getannotation(class<t> annotationtype);
其中isshadowedby()方法用于处理子类方法隐藏父类方法的问题,junit支持测试类存在继承关系,并且会遍历所有父类的测试方法,但是如果子类的方法隐藏了父类的方法,则父类的方法不会被执行。这里的隐藏是指当子类的方法名、所有参数类型相同时,不管是静态方法还是非静态方法,都会被隐藏。其实现如下:
1 public boolean isshadowedby(frameworkmethod other) {
2 if (!other.getname().equals(getname()))
3 return false;
4 if (other.getparametertypes().length != getparametertypes().length)
5 return false;
6 for (int i= 0; i < other.getparametertypes().length; i)
7 if (!other.getparametertypes()[i].equals(getparametertypes()[i]))
9 return false;
10 return true;
11 }
类似frameworkmethod,frameworkfield是对java反射中的field的封装,它提供了对字段取值、处理隐藏父类字段的问题,其主要提供的接口如下:
1 public string getname();
2 @override
3 public annotation[] getannotations();
4 public boolean ispublic();
5 @override
6 public boolean isshadowedby(frameworkfield othermember) {
7 return othermember.getname().equals(getname());
8 }
9 public boolean isstatic();
10 public field getfield();
11 public class gettype();
12 public object get(object target)
13 throws illegalargumentexception, illegalaccessexception;
在处理隐藏问题是,它只是判断如果父类中某个字段的名和子类中某个字段的名字相同,则父类中的字段不会被junit处理,即被隐藏。事实上,关于隐藏的问题,只是对junit识别的方法和字段有效,即有junit相关注解的方法字段,其他方法并不受影响(事实上也是不可能受到影响)。
testclass类
testclass是对java中class类的封装,在testclass构造过程中,它会收集所有传入class实例中具有注释的字段和方法,它会搜索类的所有继承结构。因而testclass的成员如下:
1 private final class fclass;
2 private map<class, list<frameworkmethod>> fmethodsforannotations;
3 private map<class, list<frameworkfield>> ffieldsforannotations;
并且这些成员在testclass
构造完后即已经初始化完成。在所有注解的收集过程中,它对@before
和@beforeclass
有一个特殊的处理,即父类中@before
和@beforeclass
的注解方法总是在之类之前,从而保证父类的@before
和@beforeclass
的注解方法会在子类的这些注解方法之前执行。而对@after
和@afterclass
的注解方法没有做处理,因而保持了之类的@after
和@afterclass
注解方法会在父类的这些注解方法之前执行。这段特殊逻辑是通过以下代码实现的(在addtoannotationlists()
方法中,其中runstoptobottom()
方法形象的表达了这个意思):1 if (runstoptobottom(type))
2 members.add(0, member);
3 else
4 members.add(member);
在获得测试类中所有注解的方法和字段的map后,testclass提供了对annotation对应的方法和字段的查询接口:
1 public list<frameworkmethod> getannotatedmethods(
2 class extends annotation> annotationclass);
3 public list<frameworkfield> getannotatedfields(
4 class extends annotation> annotationclass);
5 public <t> list<t> getannotatedfieldvalues(object test,
6 class extends annotation> annotationclass,
7 class<t> valueclass);
以及测试类相关的信息,如class实例、名字、测试类中具有的annotation等:
1 public class getjavaclass();
2 public string getname();
3 public constructor getonlyconstructor();
4 public annotation[] getannotations();
5 public boolean isanonstaticinnerclass();
runner、parentrunner与blockjunit4classrunner类
runner是junit中对所有runner的抽象,它只包括三个方法:
1 public abstract class runner implements describable {
2 public abstract description getdescription();
3 public abstract void run(runnotifier notifier);
4 public int testcount() {
5 return getdescription().testcount();
6 }
7 }
其中getdescription()的实现已经在description相关的小节中做过计算了,不在重复,testcount()方法很简单,也已经实现了,因而本节主要介绍run()方法的运行过程,而该方法的实现则是在parentrunner中。
用parentrunner命名这个runner是想表达这是一个在测试方法树中具有子节点的节点,它的子节点可以是测试方法(blockjunit4classrunner)或者runner(suite)。parentrunner以测试类class实例作为构造函数的参数,在构造函数内部用testclass对测试类进行封装,并对一些那些规定的方法做验证:
1. @beforeclass和@afterclass注解的方法必须是public,void,static,无参
1 validatepublicvoidnoargmethods(beforeclass.class, true, errors);
2 validatepublicvoidnoargmethods(afterclass.class, true, errors);
2. 对有@classrule修饰的字段,必须是public,static,并且该字段的实例必须是实现了testrule接口的。为了兼容性,也可以实现methodrule接口。
在验证过程中,parentrunner通过一个list收集所有的验证错误信息,如果存在错误信息,则验证不通过,parentrunner将抛出一个initializationerror的exception,该exception中包含了所有的错误信息,一般在这种情况下,会创建一个errorreportingrunner返回以处理验证出错的问题,该runner将在下面的小节中详细介绍。
在runner构造完成后,就可以调用run()方法运行该runner了:
1 @override
2 public void run(final runnotifier notifier) {
3 eachtestnotifier testnotifier= new eachtestnotifier(notifier, getdescription());
5 try {
6 statement statement= classblock(notifier);
7 statement.evaluate();
8 } catch (assumptionviolatedexception e) {
9 testnotifier.firetestignored();
10 } catch (stoppedbyuserexception e) {
11 throw e;
12 } catch (throwable e) {
13 testnotifier.addfailure(e);
14 }
15 }
该方法的核心是构造一个statement实例,然后调用该实例的evaluate()方法。在junit中,statement是一个类链表,一个runner的执行过程就是这个statement类链表的执行过程。parentrunner对statement类链表的构造主要是对测试类级别的构造,如@beforeclass、@afterclass、@classrule等执行statement:
1 protected statement classblock(final runnotifier notifier) {
2 statement statement= childreninvoker(notifier);
3 statement= withbeforeclasses(statement);
4 statement= withafterclasses(statement);
5 statement= withclassrules(statement);
6 return statement;
7 }
8 protected statement childreninvoker(final runnotifier notifier) {
9 return new statement() {
10 @override
11 public void evaluate() {
12 runchildren(notifier);
13 }
14 };
15 }
16 private void runchildren(final runnotifier notifier) {
17 for (final t each : getfilteredchildren())
18 fscheduler.schedule(new runnable() {
19 public void run() {
20 parentrunner.this.runchild(each, notifier);
21 }
22 });
23 fscheduler.finished();
24 }
25 private list<t> getfilteredchildren() {
26 if (ffilteredchildren == null)
27 ffilteredchildren = new arraylist<t>(getchildren());
28 return ffilteredchildren;
29 }
30 protected abstract list<t> getchildren();
31 protected abstract void runchild(t child, runnotifier notifier);
其中getchildren()、runchild()方法由子类实现。
1 protected statement withbeforeclasses(statement statement) {
2 list<frameworkmethod> befores= ftestclass
3 .getannotatedmethods(beforeclass.class);
4 return befores.isempty() ? statement :
5 new runbefores(statement, befores, null);
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 private statement withclassrules(statement statement) {
14 list<testrule> classrules= classrules();
15 return classrules.isempty() ? statement :
16 new runrules(statement, classrules, getdescription());
17 }
18 protected list<testrule> classrules() {
19 return ftestclass.getannotatedfieldvalues(null,
20 classrule.class, testrule.class);
21 }
关于statement将在下一节中详细讲,不过这里从statement的构造过程可以看出rule的执行要先于@beforeclass注解方法或晚于@afterclass注解方法。
在parentrunner关于执行方法的实现中,还有一个对每个测试方法statement链的执行框架的实现,其主要功能是加入事件发布和对异常的处理逻辑,从这里的statement实例是一个测试方法的运行整体,它包括了@before注解方法、@after注解方法以及@rule注解字段的testrule实例的运行过程,因而teststarted事件的发布是在@before方法运行之前,而testfinished事件的发布是在@after方法运行之后:
1 protected final void runleaf(statement statement, description description, runnotifier notifier) {
3 eachtestnotifier eachnotifier= new eachtestnotifier(notifier, description);
5 eachnotifier.fireteststarted();
6 try {
7 statement.evaluate();
8 } catch (assumptionviolatedexception e) {
9 eachnotifier.addfailedassumption(e);
10 } catch (throwable e) {
11 eachnotifier.addfailure(e);
12 } finally {
13 eachnotifier.firetestfinished();
14 }
15 }
parentrunner还实现filterable接口和sortable接口,不过这里很奇怪的实现时为什么sorter要保存成字段,感觉这个完全可以通过方法参数实现,而filteredchildren字段的实现方式在多线程环境中运行的话貌似也会出问题。filter类主要提供一个shouldrun()接口以判断传入的description是否可以运行,若否,则将该description从parentrunner中移除;否则,将该filter应用到该child中,以处理child可能是filterable实例(parentrunner)的问题,从而以递归、先根遍历的方式遍历测试实例树上的所有节点,若一个父节点的所有子节点都被过滤了,则抛出notestremainexception:
1 public void filter(filter filter) throws notestsremainexception {
2 for (iterator<t> iter = getfilteredchildren().iterator();
3 iter.hasnext(); ) {
4 t each = iter.next();
5 if (shouldrun(filter, each))
6 try {
7 filter.apply(each);
8 } catch (notestsremainexception e) {
9 iter.remove();
10 }
11 else
12 iter.remove();
13 }
14 if (getfilteredchildren().isempty()) {
15 throw new notestsremainexception();
16 }
17 }
18 private boolean shouldrun(filter filter, t each) {
19 return filter.shouldrun(describechild(each));
20 }
junit中提供几种默认实现的filter:1. all不做任何过滤;2. matchmethoddescription只保留某个指定的测试方法。
1 public static filter all= new filter() {
2 @override
3 public boolean shouldrun(description description) {
4 return true;
5 }
6 @override
7 public string describe() {
8 return "all tests";
9 }
10 @override
11 public void apply(object child) throws notestsremainexception {
12 // 因为不会做任何过滤行为,因而不需要应用到子节点中
13 }
14 @override
15 public filter intersect(filter second) {
16 return second; // 因为本身没有任何过滤行为,所以可以直接返回传入的filter
17 }
18 };
19 public static filter matchmethoddescription(final description desireddescription) {
21 return new filter() {
22 @override
23 public boolean shouldrun(description description) {
24 if (description.istest())
25 return desireddescription.equals(description);
26 // explicitly check if any children want to run
27 for (description each : description.getchildren())
28 if (shouldrun(each))
29 return true;
30 return false;
31 }
32 @override
33 public string describe() {
34 return string.format("method %s", desireddescription.getdisplayname());
36 }
37 };
38 }
sorter类实现comparator接口,parentrunner通过其compare()方法构造和parentrunner子节点相关的comparator实例,并采用后根递归遍历的方式对测试方法树中的所有测试方法进行排序,因而这里只会排序同一个节点下的所有子节点,而节点之间的顺序不会受影响。
1 public void sort(sorter sorter) {
2 fsorter= sorter;
3 for (t each : getfilteredchildren())
4 sortchild(each);
5 collections.sort(getfilteredchildren(), comparator());
6 }
7 private comparator super t> comparator() {
8 return new comparator<t>() {
9 public int compare(t o1, t o2) {
10 return fsorter.compare(describechild(o1), describechild(o2));
12 }
13 };
14 }
最后parentrunner还提供了一个runnerscheduler的接口字段,以控制junit测试方法的执行过程,我们可以注入一个使用多线程方式运行每个测试方法的runnerscheduler,不过从代码上看,貌似junit对多线程的支持并不好,所以这个接口的扩展目前来看我还找不到什么用途:
1 public interface runnerscheduler {
2 void schedule(runnable childstatement);
3 void finished();
4 }
blockjunit4classrunner节点下所有的子节点都是测试方法,它是junit中运行测试方法的核心runner,它实现了parentrunner中没有实现的几个方法:
1 @override
2 protected list<frameworkmethod> getchildren() {
3 return computetestmethods();
4 }
5 protected list<frameworkmethod> computetestmethods() {
6 return gettestclass().getannotatedmethods(test.class);
7 }
8 @override
9 protected void runchild(final frameworkmethod method, runnotifier notifier) {
11 description description= describechild(method);
12 if (method.getannotation(ignore.class) != null) {
13 notifier.firetestignored(description);
14 } else {
15 runleaf(methodblock(method), description, notifier);
16 }
17 }
18 protected statement methodblock(frameworkmethod method) {
19 object test;
20 try {
21 test= new reflectivecallable() {
22 @override
23 protected object runreflectivecall() throws throwable {
24 return createtest();
25 }
26 }.run();
27 } catch (throwable e) {
28 return new fail(e);
29 }
30 statement statement= methodinvoker(method, test);
31 statement= possiblyexpectingexceptions(method, test, statement);
32 statement= withpotentialtimeout(method, test, statement);
33 statement= withbefores(method, test, statement);
34 statement= withafters(method, test, statement);
35 statement= withrules(method, test, statement);
36 return statement;
37 } //这个方法的实现可以看出每个测试方法运行时都会重新创建一个新的测试类实例,这也可能是@beforeclass、afterclass、@classrule需要静态的原因吧,因为静态的话,每次类实例的重新创建对其结果都不会有影响。
38 另,从这里对statement的构建顺序,junit对testrule的运行也要在@before注解方法之前或@after注解方法之后
39 protected object createtest() throws exception {
40 return gettestclass().getonlyconstructor().newinstance();
41 }
42 protected statement methodinvoker(frameworkmethod method, object test) {
43 return new invokemethod(method, test);
44 }
45 protected statement possiblyexpectingexceptions(frameworkmethod method, object test, statement next) {
47 test annotation= method.getannotation(test.class);
48 return expectsexception(annotation) ? new expectexception(next,
49 getexpectedexception(annotation)) : next;
50 }
51 private class extends throwable> getexpectedexception(test annotation) {
52 if (annotation == null || annotation.expected() == none.class)
53 return null;
54 else
55 return annotation.expected();
56 }
57 private boolean expectsexception(test annotation) {
58 return getexpectedexception(annotation) != null;
59 }
60 protected statement withpotentialtimeout(frameworkmethod method,
61 object test, statement next) {
62 long timeout= gettimeout(method.getannotation(test.class));
63 return timeout > 0 ? new failontimeout(next, timeout) : next;
64 }
65 private long gettimeout(test annotation) {
66 if (annotation == null)
67 return 0;
68 return annotation.timeout();
69 }
70 protected statement withbefores(frameworkmethod method, object target,
71 statement statement) {
72 list<frameworkmethod> befores= gettestclass().getannotatedmethods(
73 before.class);
74 return befores.isempty() ? statement : new runbefores(statement,
75 befores, target);
76 }
77 protected statement withafters(frameworkmethod method, object target,
78 statement statement) {
79 list<frameworkmethod> afters= gettestclass().getannotatedmethods(
80 after.class);
81 return afters.isempty() ? statement : new runafters(statement,
82 afters, target);
83 }
84 private statement withrules(frameworkmethod method, object target,
85 statement statement) {
86 statement result= statement;
87 result= withmethodrules(method, target, result);
88 result= withtestrules(method, target, result);
89 return result;
90 }
91 private statement withmethodrules(frameworkmethod method, object target,
92 statement result) {
93 list<testrule> testrules= gettestrules(target);
94 for (org.junit.rules.methodrule each : getmethodrules(target))
95 if (! testrules.contains(each))
96 result= each.apply(result, method, target);
97 return result;
98 }
99 private list<org.junit.rules.methodrule> getmethodrules(object target) {
100 return rules(target);
101 }
102 protected list<org.junit.rules.methodrule> rules(object target) {
103 return gettestclass().getannotatedfieldvalues(target, rule.class,
104 org.junit.rules.methodrule.class);
105 }
106 private statement withtestrules(frameworkmethod method, object target,
107 statement statement) {
108 list<testrule> testrules= gettestrules(target);
109 return testrules.isempty() ? statement :
110 new runrules(statement, testrules, describechild(method));
111 }
112 protected list<testrule> gettestrules(object target) {
113 return gettestclass().getannotatedfieldvalues(target,
114 rule.class, testrule.class);
115 }
在验证方面,blockjunit4classrunner也加入了一些和自己相关的验证:
1 @override
2 protected void collectinitializationerrors(list<throwable> errors) {
3 super.collectinitializationerrors(errors);
4 validatenononstaticinnerclass(errors);
5 validateconstructor(errors);
6 validateinstancemethods(errors);
7 validatefields(errors);
8 }
1. 如果测试类是一个类的内部类,那么该测试类必须是静态的:
1 protected void validatenononstaticinnerclass(list<throwable> errors) {
2 if (gettestclass().isanonstaticinnerclass()) {
3 string gripe= "the inner class " gettestclass().getname()
4 " is not static.";
5 errors.add(new exception(gripe));
6 }
7 }
2. 测试类的构造函数必须有且仅有一个无参的构造函数(事实上关于只有一个构造函数的验证在构造testclass实例的时候已经做了,因而这里真正起作用的知识对无参的验证):
1 protected void validateconstructor(list<throwable> errors) {
2 validateonlyoneconstructor(errors);
3 validatezeroargconstructor(errors);
4 }
5 protected void validateonlyoneconstructor(list<throwable> errors) {
6 if (!hasoneconstructor()) {
7 string gripe= "test class should have exactly one public constructor";
8 errors.add(new exception(gripe));
9 }
10 }
11 protected void validatezeroargconstructor(list<throwable> errors) {
12 if (!gettestclass().isanonstaticinnerclass()
13 && hasoneconstructor()
14 && (gettestclass().getonlyconstructor().getparametertypes().length != 0)) {
16 string gripe= "test class should have exactly one public zero-argument constructor";
17 errors.add(new exception(gripe));
18 }
19 }
20 private boolean hasoneconstructor() {
21 return gettestclass().getjavaclass().getconstructors().length == 1;
22 }
3. @before、@after、@test注解的方法必须是public,void,非静态,不带参数:
1 protected void validateinstancemethods(list<throwable> errors) {
2 validatepublicvoidnoargmethods(after.class, false, errors);
3 validatepublicvoidnoargmethods(before.class, false, errors);
4 validatetestmethods(errors);
5 if (computetestmethods().size() == 0)
6 errors.add(new exception("no runnable methods"));
7 }
8 protected void validatetestmethods(list<throwable> errors) {
9 validatepublicvoidnoargmethods(test.class, false, errors);
10 }
4. 带有@rule注解的字段必须是public,非静态,实现了testrule接口或methodrule接口。
1 private void validatefields(list<throwable> errors) {
2 rule_validator.validate(gettestclass(), errors);
3 }
junit核心执行的序列图
写了好几天,终于把junit核心的执行所有代码分析完了,不过发现写的那么细,很多东西其实很难表达,因而贴了很多代码,感觉写的挺乱的,所以画一张序列图吧,感觉很多时候还是图的表达效果更好一些,要我看那么一大段的问题,也感觉挺烦的。
suite与parameterized
suite是其子节点是runner的runner,其内部保存了一个runner的list。suite的功能实现很简单,因为它将大部分的方法代理给了其内部runner实例:
1 private final list<runner> frunners;
2 @override
3 protected list<runner> getchildren() {
4 return frunners;
5 }
6 @override
7 protected description describechild(runner child) {
8 return child.getdescription();
9 }
10 @override
11 protected void runchild(runner runner, final runnotifier notifier) {
12 runner.run(notifier);
13 }
suite重点在于如何构建一个suite,suite提供两个构造函数:
1. 提供一个带suiteclasses注解的类,所有测试类由suiteclasses指定,而klass类作为这个suite的根类。此时一般所有的测试类是klass的内部类,因而可以通过@runwith指定运行klass类的runner是suite,然后用suiteclasses注解指定可以作为测试类的类。
1 public suite(class klass, runnerbuilder builder) throws initializationerror {
3 this(builder, klass, getannotatedclasses(klass));
4 }
5 protected suite(runnerbuilder builder, class klass, class[] suiteclasses) throws initializationerror {
7 this(klass, builder.runners(klass, suiteclasses));
8 }
9 @retention(retentionpolicy.runtime)
10 @target(elementtype.type)
11 @inherited
12 public @interface suiteclasses {
13 public class[] value();
14 }
15 private static class[] getannotatedclasses(class klass) throws initializationerror {
17 suiteclasses annotation= klass.getannotation(suiteclasses.class);
18 if (annotation == null)
19 throw new initializationerror(string.format("class '%s' must have a suiteclasses annotation", klass.getname()));
22 return annotation.value();
23 }
2. 在有些情况下,我们需要运行多个没有相关的测试类,此时这些测试类没有一个公共的根类的suite,则需要使用一下的构造函数(此时suite的getname()返回”null”,即其description的displayname的值为”null”,关于runnerbuilder将会在后面的文章中介绍):
1 public suite(runnerbuilder builder, class[] classes) throws initializationerror {
3 this(null, builder.runners(null, classes));
4 }
5 protected suite(class klass, list<runner> runners) throws initializationerror {
7 super(klass);
8 frunners = runners;
9 }
parameterized继承自suite,它类似blockjunit4classrunner是对一个测试类的运行过程的封装,但是它支持在测试类中定义一个获得参数数组的一个列表,从而可以为构造该测试类提供不同的参数值以进行多次测试。因而parameterized这个runner其实就是根据参数数组列表的个数创建多个基于该测试类的runner,并运行所有这些runner中测试方法。
由于这个测试类的构造函数是带参的,而blockjunit4classrunner则限制其测试类必须是不带参的,因而parameterized需要创建自己的runner,它集成自blockjunit4classrunner,除了构造函数的差别,parameterized的根节点是该测试类本身,它处理@beforeclass、@afterclass、@classrule的注解问题,因而其子节点的runner不需要重新对其做处理了,因而在集成的runner中应该避免这些方法、字段重复执行。
1 private class testclassrunnerforparameters extends blockjunit4classrunner {
3 private final int fparametersetnumber;
4 private final list<object[]> fparameterlist;
5 testclassrunnerforparameters(class type, list<object[]> parameterlist, int i) throws initializationerror {
8 super(type);
9 fparameterlist= parameterlist;
10 fparametersetnumber= i; //参数数组列表中的位置
11 }
12 @override
13 public object createtest() throws exception {
14 return gettestclass().getonlyconstructor().newinstance(
15 computeparams());//带参创建测试类实例
16 }
17 private object[] computeparams() throws exception {
18 try {
19 return fparameterlist.get(fparametersetnumber);
20 } catch (classcastexception e) {
21 throw new exception(string.format(
22 "%s.%s() must return a collection of arrays.",
23 gettestclass().getname(), getparametersmethod(
24 gettestclass()).getname()));
25 }
26 }
27 @override
28 protected string getname() {
29 return string.format("[%s]", fparametersetnumber);
30 }
31 @override
32 protected string testname(final frameworkmethod method) {
33 return string.format("%s[%s]", method.getname(),
34 fparametersetnumber);
35 }
36 @override
37 protected void validateconstructor(list<throwable> errors) {
38 validateonlyoneconstructor(errors);//去除不带参构造函数验证
39 }
40 @override
41 protected statement classblock(runnotifier notifier) {
42 return childreninvoker(notifier);//不处理@beforeclass、
43 //@afterclass、@classrule等注解
44 }
45 @override
46 protected annotation[] getrunnerannotations() {
47 return new annotation[0];//去除测试类的annotation,这些应该在parameterized中处理
49 }
50 }
parameterized对suite的行为改变只是在创建runner集合的过程,parameterized通过类中查找@parameters注解的静态方法获得参数数组列表,并更具这个参数数组列表创建testclassrunnerforparameters的集合:
1 @retention(retentionpolicy.runtime)
2 @target(elementtype.method)
3 public static @interface parameters {
4 }
5 public parameterized(class klass) throws throwable {
6 super(klass, collections.<runner>emptylist());
7 list<object[]> parameterslist= getparameterslist(gettestclass());
8 for (int i= 0; i < parameterslist.size(); i)
9 runners.add(new testclassrunnerforparameters(gettestclass().getjavaclass(), parameterslist, i));
11 }
12 private list<object[]> getparameterslist(testclass klass)
13 throws throwable {
14 return (list<object[]>) getparametersmethod(klass).invokeexplosively(null);
16 }
17 private frameworkmethod getparametersmethod(testclass testclass)
18 throws exception {
19 list<frameworkmethod> methods= testclass.getannotatedmethods(parameters.class);
21 for (frameworkmethod each : methods) {
22 int modifiers= each.getmethod().getmodifiers();
23 if (modifier.isstatic(modifiers) &&
24 modifier.ispublic(modifiers))
25 return each;
26 }
27 throw new exception("no public static parameters method on class " testclass.getname());
29 }
一个简单的parameterized测试类如下,不过如果在eclipse中单独的运行testequal()测试方法会出错,因为eclipse在filter传入的方法名为testequal,而在parameterized中这个方法名已经被改写成testequal[i]了,因而在filter过程中,所有的测试方法都被过滤掉了,此时会抛notestremainexception,而在eclipse中出现的则是initializationerror的exception,这里也是eclipse中junit的插件没有做好,它并没有给出failure类的详细信息:
1 public class parameterizedtest {
2 @parameters
3 public static list<object[]> data() {
4 return arrays.aslist(new object[][] {
5 { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 },
6 { 4, 3 }, { 5, 5 }, { 6, 8 }
7 });
8 }
9 @beforeclass
10 public static void beforeclass() {
11 system.out.println(".testing.");
12 }
13 private final int input;
14 private final int expected;
15 public parameterizedtest(int input, int expected) {
16 this.input = input;
17 this.expected = expected;
18 }
19 @test
20 public void testequal() {
21 assert.assertequals(expected, compute(input));
22 }
23 public int compute(int input) {
24 if(input == 0 || input == 1) {
25 return input;
26 }
27 if(input == 2) {
28 return 1;
29 }
30 return compute(input -1) compute(input - 2);
31 }
32 }
errorreportingrunner和ignoredclassrunner
关于junit4,还剩下最后两个runner,分别是errorreportingrunner和ignoredclassrunner,为了编程模型的统一(这是一个非常好的设计想法,将各种变化都封装在runner中),junit中即使一个runner实例创建失败或是该测试类有@ignored的注解,在这两种情况中,junit分别通过errorreportingrunner和ignoredclassrunner去表达。在errorreportingrunner中,为每个exception发布测试失败的信息;ignoredclassrunner则只是发布testignored事件:
1 public class errorreportingrunner extends runner {
2 private final list<throwable> fcauses;
3 private final class ftestclass;
4 public errorreportingrunner(class testclass, throwable cause) {
5 ftestclass= testclass;
6 fcauses= getcauses(cause);
7 }
8 @override
9 public description getdescription() {
10 description description= description.createsuitedescription(ftestclass);
12 for (throwable each : fcauses)
13 description.addchild(describecause(each));
14 return description;
15 }
16 @override
17 public void run(runnotifier notifier) {
18 for (throwable each : fcauses)
19 runcause(each, notifier);
20 }
21 @suppresswarnings("deprecation")
22 private list<throwable> getcauses(throwable cause) {
23 if (cause instanceof invocationtargetexception)
24 return getcauses(cause.getcause());
25 if (cause instanceof initializationerror)
26 return ((initializationerror) cause).getcauses();
27 if (cause instanceof org.junit.internal.runners.initializationerror)
28 return ((org.junit.internal.runners.initializationerror) cause)
29 .getcauses();
30 return arrays.aslist(cause);
31 }
32 private description describecause(throwable child) {
33 return description.createtestdescription(ftestclass,
34 "initializationerror");
35 }
36 private void runcause(throwable child, runnotifier notifier) {
37 description description= describecause(child);
38 notifier.fireteststarted(description);
39 notifier.firetestfailure(new failure(description, child));
40 notifier.firetestfinished(description);
41 }
42 }
43 public class ignoredclassrunner extends runner {
44 private final class ftestclass;
45 public ignoredclassrunner(class testclass) {
46 ftestclass= testclass;
47 }
48 @override
49 public void run(runnotifier notifier) {
50 notifier.firetestignored(getdescription());
51 }
52 @override
53 public description getdescription() {
54 return description.createsuitedescription(ftestclass);
55 }
56 }
写在最后的话
写了一个周末了,这一篇终于是写完了,初次尝试将阅读框架源码以文字的形式表达出来,确实不好写,感觉自己写的太详细了,也贴了太多源码,因为有些时候感觉源码比文字更有表达能力,不过太多的源码就把很多实质的东西掩盖了,然后文章看起来也没有什么大的价值干,而且太长了,看到那么多的东西,要我自己看到也就有不想看的感觉,1万多字啊,呵呵。总之以后慢慢改进吧,这一篇就当是练习了,不想复工了,也没那么多的时间重写。。。。。。