初次用文字的方式記錄讀源碼的過程,不知道怎麼寫,感覺有點貼代碼的嫌疑。不過中間還是加入了一些自己的了解和心得,希望以後能夠慢慢的改進,感興趣的童鞋湊合着看吧,感覺junit這個架構還是值得看的,裡面有許多不錯的設計思想在,更何況它是kent beck和erich gamma這樣的大師寫的。。。。。
經過前面三節的runner、statement、rule的講解,事實上junit的核心運作邏輯已經完成了,剩下的就是一些外圍的支援代碼,包括runner的建構、用assert對測試方法的運作結果的驗證代碼以及為了相容性而存在的一些代碼。本節将關注runner的建構部分,在junit中通過request和runnerbuilder共同支援。
在junit中,runnerbuilder對根據測試類建立runner邏輯的封裝,特别是它支援@runwith注解以在測試類中指定需要的執行的runner,這也是自定義的runner可以很友善的插入junit架構運作的原因,這個設計其實也蠻具有參考價值的:通過注解的方式為使用者提供插入點,以擴充架構本身的功能;而在實作工程中,通過外圍builder來支援,進而不影響核心設計。
request有點類似配置執行個體的感覺,使用者可以根據自己的需求來定制runner建立的資訊,如代理給runnerbuilder為每個測試類建立相應的runner,根據使用者的需求以決定是否要有filter和sort操作等。使用者可以定義自己的request執行個體,并傳遞給junitcore以實作自己特定的需求。
在現實中有可能存在這樣的一個需求,即使用者想同時運作多個測試類,而且這些測試類之間又是互相獨立的,在junit中,使用suite來表達這個需求,但是在request中并沒有一個單獨的request接收多個class執行個體以建立suite這個runner,而是它使用了一個獨立的類computer來完成這樣的功能,在request中定義一個靜态方法來處理這個問題。不過我很奇怪為什麼要這麼做,這個設計和現存的程式設計模型是格格不入的,個人不太贊同。
大體介紹了runnerbuilder和request的用途和設計,接下來将詳細介紹他們的源碼實作,先從runnerbuilder開始。
runnerbuilder的核心就是根據給出的class執行個體,建立相應的runner。一般建立runner遵循的邏輯是:
1. 如果class中有@ignored注解,那麼将建立ignoredclassrunner,該runner在運作時什麼都不做,隻是觸發testignored事件。
2. 如果class中有@runwith注解,則使用@runwith注解中指定的runner。
3. 如果class中有靜态suite()方法的存在,則使用suitemethod這個runner(相容junit3)。
4. 如果class繼承了testcase類,則使用junit38classrunner(相容junit3)。
5. 否則,使用blockjunit4classrunner(junit4中預設的runner)。
其實這個邏輯就是alldefaultpossibilitiesbuilder中構造runner的實作。在junit内部大量的使用這個runnerbuilder來構造runner。其源碼如下:
1 public class alldefaultpossibilitiesbuilder extends runnerbuilder {
2 private final boolean fcanusesuitemethod;
3 public alldefaultpossibilitiesbuilder(boolean canusesuitemethod) {
4 fcanusesuitemethod= canusesuitemethod;
5 }
6 @override
7 public runner runnerforclass(class<?> testclass) throws throwable {
8 list<runnerbuilder> builders= arrays.aslist(
9 ignoredbuilder(),
10 annotatedbuilder(),
11 suitemethodbuilder(),
12 junit3builder(),
13 junit4builder());
14 for (runnerbuilder each : builders) {
15 runner runner= each.saferunnerforclass(testclass);
16 if (runner != null)
17 return runner;
18 }
19 return null;
20 }
21 protected junit4builder junit4builder() {
22 return new junit4builder();
23 }
24 protected junit3builder junit3builder() {
25 return new junit3builder();
26 }
27 protected annotatedbuilder annotatedbuilder() {
28 return new annotatedbuilder(this);
29 }
30 protected ignoredbuilder ignoredbuilder() {
31 return new ignoredbuilder();
32 }
33 protected runnerbuilder suitemethodbuilder() {
34 if (fcanusesuitemethod)
35 return new suitemethodbuilder();
36 return new nullbuilder();
37 }
38 }
其中fcanusesuitemethod用于表達測試類中靜态的suite()方法是否被視為用于獲得多個執行個體運作的方法,這個是為了相容junit3而存在,而且在junit内部的使用時一般都是給true。再加上junit3builder()放在junit4builder()之前構造runnerbuilder,表明為了相容junit3,junit4以junit3中的風格為首選風格。
這裡将不對為了相容junit3而建立的runnerbuilder做介紹,因而下面隻會介紹ignoredbuilder、annotatedbuilder、junit4builder,事實上它都太簡單了,以至于基本上不用什麼介紹了。ignoredbuilder會檢查傳入的class執行個體是否有@ignored注解,若有,則建立ignoredclassrunner,否則傳回null;annotatedbuilder檢查傳入的class執行個體是否有@runwith注解,若有,則使用@runwith注解中指定的runner,否則,傳回null,這裡需要注意的是在使用者自定義的runner中,必須包含一個以class執行個體作為參數的構造函數,或者以class執行個體和runnerbuilder執行個體作為參數的構造函數,否則在構造自定義的runner時會出錯;junit4builder直接根據傳入的測試類class的執行個體建立blockjunit4classrunner。
1 public class ignoredbuilder extends runnerbuilder {
2 @override
3 public runner runnerforclass(class<?> testclass) {
4 if (testclass.getannotation(ignore.class) != null)
5 return new ignoredclassrunner(testclass);
6 return null;
7 }
8 }
9 public class annotatedbuilder extends runnerbuilder {
10 private static final string constructor_error_format= "custom runner class %s should have a public constructor with signature %s(class testclass)";
11 private runnerbuilder fsuitebuilder;
12 public annotatedbuilder(runnerbuilder suitebuilder) {
13 fsuitebuilder= suitebuilder;
14 }
15 @override
16 public runner runnerforclass(class<?> testclass) throws exception {
17 runwith annotation= testclass.getannotation(runwith.class);
18 if (annotation != null)
19 return buildrunner(annotation.value(), testclass);
20 return null;
21 }
22 public runner buildrunner(class<? extends runner> runnerclass,
23 class<?> testclass) throws exception {
24 try {
25 return runnerclass.getconstructor(class.class).newinstance(
26 new object[] { testclass });
27 } catch (nosuchmethodexception e) {
28 try {
29 return runnerclass.getconstructor(class.class,
30 runnerbuilder.class).newinstance(
31 new object[] { testclass, fsuitebuilder });
32 } catch (nosuchmethodexception e2) {
33 string simplename= runnerclass.getsimplename();
34 throw new initializationerror(string.format(
35 constructor_error_format, simplename, simplename));
36 }
37 }
38 }
39 }
40 public class junit4builder extends runnerbuilder {
41 @override
42 public runner runnerforclass(class<?> testclass) throws throwable {
43 return new blockjunit4classrunner(testclass);
44 }
45 }
而runbuilder類本身也定義了一些方法,以幫助其他runner,如suite,建構其内部通過其他方式取到的測試類class執行個體。這裡對parent字段的存在有必要解釋一下,因為我剛開始看到的時候也很費解,addparent()方法隻在一個方法中調用一次,而且就這個類來看也不存在遞歸,為什麼會有對相同parents的驗證?要解釋這個問題,需要知道suite的構造函數還會調用runners()方法,加入有一次調用parent為一個使用suite的類,這個類同時又在children中出現,那麼在調用該方法使将給類加入到parents中,而後在構造children中的該類時又會調用該方法,将想用的class執行個體加入parents中,進而引起異常。
1 public abstract class runnerbuilder {
2 private final set<class<?>> parents= new hashset<class<?>>();
3 public abstract runner runnerforclass(class<?> testclass) throws throwable;
4 public runner saferunnerforclass(class<?> testclass) {
5 try {
6 return runnerforclass(testclass);
7 } catch (throwable e) {
8 return new errorreportingrunner(testclass, e);
9 }
10 }
11 class<?> addparent(class<?> parent) throws initializationerror {
12 if (!parents.add(parent))
13 throw new initializationerror(string.format("class '%s' (possibly indirectly) contains itself as a suiteclass", parent.getname()));
14 return parent;
15 }
16 void removeparent(class<?> klass) {
17 parents.remove(klass);
18 }
19 public list<runner> runners(class<?> parent, class<?>[] children)
20 throws initializationerror {
21 addparent(parent);
22 try {
23 return runners(children);
24 } finally {
25 removeparent(parent);
26 }
27 }
28 public list<runner> runners(class<?> parent, list<class<?>> children)
29 throws initializationerror {
30 return runners(parent, children.toarray(new class<?>[0]));
31 }
32 private list<runner> runners(class<?>[] children) {
33 arraylist<runner> runners= new arraylist<runner>();
34 for (class<?> each : children) {
35 runner childrunner= saferunnerforclass(each);
36 if (childrunner != null)
37 runners.add(childrunner);
38 }
39 return runners;
40 }
41 }
最後來看一下runnerbuilder的類結構圖吧,了解一下目前存在的幾個runnerbuilder。
request是對runnerbuilder的封裝,它提供了改變runnerbuilder建立出的runner的接口,如建立runner後,用filter或sorter過濾或重新排列測試方法的順序。就目前junit隻有filter和sorter可以對runner做一些自定義的配置。filter可以定義那些測試方法是可以運作的,比如在eclipse中提供的對一個測試方法單獨運作就是使用它來實作;或者使用者可以自己定義一個可以運作方法的集合,然後隻要遇到這樣的方法,然後根據這個集合來編寫自定義的filter。sorter則用于排列runner内部測試方法的執行順序,但是這個定制隻是對一個runner中的測試方法有用,它并不會排列跨runner之間的測試方法。不廢話了,先來看一下request的類結構圖吧。
request的結構比較簡單,而且代碼實作也比較簡單,request是一個抽象類,它定義了一個getrunner()的抽象方法,這個方法隻是傳回一個runner執行個體。其中classrequest根據一個測試類,使用alldefaultpossibilitiesbuilder建立一個runner;filterrequest則以一個request和filter執行個體為構造參數,在實作getrunner()方法時,根據傳入的request擷取runner,并對改runner應用傳入的filter以過濾掉那些不需要運作的測試方法;sortingrequest也是以一個request和comparator<description>為構造參數,在實作getrunner()方法是,根據傳入的request擷取runner,并根據comparator構造sorter對剛擷取到的runner排序。這些實作有點decorator模式的味道。
1 public class classrequest extends request {
2 private final class<?> ftestclass;
3 private boolean fcanusesuitemethod;
4 public classrequest(class<?> testclass, boolean canusesuitemethod) {
5 ftestclass= testclass;
6 fcanusesuitemethod= canusesuitemethod;
8 public classrequest(class<?> testclass) {
9 this(testclass, true);
11 @override
12 public runner getrunner() {
13 return new alldefaultpossibilitiesbuilder(fcanusesuitemethod).saferunnerforclass(ftestclass);
16 }
17 public final class filterrequest extends request {
18 private final request frequest;
19 private final filter ffilter;
20 public filterrequest(request classrequest, filter filter) {
21 frequest= classrequest;
22 ffilter= filter;
24 @override
25 public runner getrunner() {
26 try {
27 runner runner= frequest.getrunner();
28 ffilter.apply(runner);
29 return runner;
30 } catch (notestsremainexception e) {
31 return new errorreportingrunner(filter.class, new exception(string
32 .format("no tests found matching %s from %s", ffilter
33 .describe(), frequest.tostring())));
34 }
35 }
36 }
37 public class sortingrequest extends request {
38 private final request frequest;
39 private final comparator<description> fcomparator;
40 public sortingrequest(request request, comparator<description> comparator) {
41 frequest= request;
42 fcomparator= comparator;
43 }
44 @override
45 public runner getrunner() {
46 runner runner= frequest.getrunner();
47 new sorter(fcomparator).apply(runner);
48 return runner;
49 }
50 }
51 public abstract class request {
52 public abstract runner getrunner();
53 }
除了request類結構,request類本身還提供了多個工場方法,以一種不需要知道request類結構的方法建立request,也算是一種封裝吧,使用起來比較友善,而且随着架構的演化,可以添加或删除子類而不需要考慮使用者是否使用了某個子類。如果做的安全一些、然後不考慮測試的話,可以把filterrequest和sortingrequest的可見性降低,如包級别的。除了一些靜态的工場方法,request為filter和sorter也提供了各自的方法支援,在我們得到一個request的引用後,隻需要調用這兩個方法即可構造需要的request(filterrequest或sortingrequest)。
1 public abstract class request {
2 public static request method(class<?> clazz, string methodname) {
3 description method= description.createtestdescription(clazz, methodname);
4 return request.aclass(clazz).filterwith(method);
6 public static request aclass(class<?> clazz) {
7 return new classrequest(clazz);
8 }
9 public static request classwithoutsuitemethod(class<?> clazz) {
10 return new classrequest(clazz, false);
11 }
12 public static request classes(computer computer, class<?>
classes) {
13 try {
14 alldefaultpossibilitiesbuilder builder= new alldefaultpossibilitiesbuilder(true);
15 runner suite= computer.getsuite(builder, classes);
16 return runner(suite);
17 } catch (initializationerror e) {
18 throw new runtimeexception(
19 "bug in saff's brain: suite constructor, called as above, should always complete");
20 }
22 public static request classes(class<?>
23 return classes(junitcore.defaultcomputer(), classes);
24 }
25 public static request runner(final runner runner) {
26 return new request(){
27 @override
28 public runner getrunner() {
29 return runner;
30 }
31 };
33 public request filterwith(filter filter) {
34 return new filterrequest(this, filter);
36 public request filterwith(final description desireddescription) {
37 return filterwith(filter.matchmethoddescription(desireddescription));
39 public request sortwith(comparator<description> comparator) {
40 return new sortingrequest(this, comparator);
41 }
42 }
43 public class computer {
44 public static computer serial() {
45 return new computer();
46 }
47 public runner getsuite(final runnerbuilder builder,
48 class<?>[] classes) throws initializationerror {
49 return new suite(new runnerbuilder() {
50 @override
51 public runner runnerforclass(class<?> testclass) throws throwable {
52 return getrunner(builder, testclass);
53 }
54 }, classes);
55 }
56 protected runner getrunner(runnerbuilder builder, class<?> testclass) throws throwable {
57 return builder.runnerforclass(testclass);
58 }
59 }
這裡對computer這個類引入的意義一直沒有弄明白,為什麼不直接在request.classes()方法中建立suite?即使要提取到computer中以給建立runner提供擴充點,直接在getsuite()方法中使用builder建立suite就可以了啊,但是又要将getrunner()方法提取出來,這個提取可以給子類在根據builder和testclass建立runner提供擴充點,但是以我目前的水準,還是看不多這個擴充點存在的意義。
junitcore是junit中運作request的門面類,同時它也提供了對指令模式的測試實作,它接收多個測試類作為參數,然後運作這些測試類中的所有測試方法。其實作是從傳入的參數中取到所有的測試類的class執行個體,然後根據這些測試類的class執行個體建立request執行個體,從建立的request執行個體中可以取得runner執行個體,運作該runner,并處理事件邏輯,最後如果所有測試通過,則退出值為0,否則為1。為了統計測試結果資訊,junit還提供了一個預設的runlistener實作:textrunlistener,這個listener在每個測試方法開始的時候列印一個點’.’,當一個測試方法失敗是列印e,當一個測試方法被忽略時列印i,當所有測試方法執行完成後列印總體統計時間,如運作時間、所有錯誤資訊的異常堆棧以及最後成功多少、失敗多少等資訊。對于基于junit編寫更适合項目本身的測試運作的使用者來說,最重要的就是幾個run()方法,這些使用者可以通過實作自己特定的邏輯以建立出符合自己需求的request或通過某種方式查找到所有自己要運作的測試類等,然後調用你需要的run()方法。
1 public class junitcore {
2 private runnotifier fnotifier;
3 public junitcore() {
4 fnotifier= new runnotifier();
6 public static result runclasses(computer computer, class<?>
7 return new junitcore().run(computer, classes);
9 public static result runclasses(class<?>
10 return new junitcore().run(defaultcomputer(), classes);
12 public result run(class<?>
13 return run(request.classes(defaultcomputer(), classes));
15 public result run(computer computer, class<?>
16 return run(request.classes(computer, classes));
17 }
18 public result run(request request) {
19 return run(request.getrunner());
21 public result run(runner runner) {
22 result result= new result();
23 runlistener listener= result.createlistener();
24 fnotifier.addfirstlistener(listener);
25 try {
26 fnotifier.firetestrunstarted(runner.getdescription());
27 runner.run(fnotifier);
28 fnotifier.firetestrunfinished(result);
29 } finally {
30 removelistener(listener);
31 }
32 return result;
33 }
34 }