初次用文字的方式記錄讀源碼的過程,不知道怎麼寫,感覺有點貼代碼的嫌疑。不過中間還是加入了一些自己的了解和心得,希望以後能夠慢慢的改進,感興趣的童鞋湊合着看吧,感覺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将會在下一節中詳細講解):