為什么JUnit里面會(huì)出現(xiàn)這樣奇怪的依賴(lài)關(guān)系,還有違反單一職責(zé)原則的TestResult?當(dāng)我看到j(luò)unit.extentions包中的TestSetup時(shí),也許我猜到了作者的用意。我們來(lái)看下TestSetup中有關(guān)的代碼:
public void run(final TestResult result) {
//又看到了上面類(lèi)似的匿名內(nèi)部類(lèi)
Protectable p= new Protectable() {
public void protect() throws Exception {
//不過(guò)這個(gè)內(nèi)部類(lèi)里面的實(shí)現(xiàn)有所不同
setUp();
basicRun(result);
tearDown();
}
};
//調(diào)用了TestResult中的runProtected方法來(lái)執(zhí)行上面的實(shí)現(xiàn)
result.runProtected(this, p);
}
這個(gè)類(lèi)的產(chǎn)生是為了彌補(bǔ)TestCase類(lèi)的一個(gè)小小的缺陷(具體請(qǐng)見(jiàn)下部分)。注意到在這個(gè)類(lèi)里面也有和TestResult類(lèi)似的匿名內(nèi)部類(lèi)。這種匿名內(nèi)部類(lèi)全是Protected接口的無(wú)名實(shí)現(xiàn),這里的目的我認(rèn)為有兩點(diǎn):
1) 由于內(nèi)部類(lèi)可以在接下來(lái)的情景中完全不可見(jiàn),而且不被任何人使用,因此也隱藏了接口的實(shí)現(xiàn)細(xì)節(jié)。
2) 為了提高可重用性,而使用內(nèi)部類(lèi)比較快捷。這樣不管你protect方法里面具體執(zhí)行什么,對(duì)它錯(cuò)誤、失敗、異常捕捉的代碼(TestResult中的runProtected方法)可以重用了。
這也正是為什么會(huì)出現(xiàn)上面那樣奇怪的依賴(lài)關(guān)系:為了復(fù)用,要讓runProtected方法放在一個(gè)TestCase和TestSetup都能調(diào)用的地方。
不過(guò)我認(rèn)為為了復(fù)用而破壞了系統(tǒng)良好的結(jié)構(gòu)和可讀性,是需要仔細(xì)斟酌的。JUnit這樣的設(shè)計(jì)估計(jì)是為了以后框架多次擴(kuò)展后的重用考慮的。
說(shuō)完了讓我費(fèi)解的問(wèn)題。談?wù)勎矣X(jué)得JUnit框架中讓我感嘆的地方,那是小小的框架里面使用了很多設(shè)計(jì)模式在里面。而這些模式的使用也正是為了體現(xiàn)出整個(gè)框架結(jié)構(gòu)的簡(jiǎn)潔、可擴(kuò)展。我將粗略的分析如下(模式應(yīng)用的詳細(xì)內(nèi)容請(qǐng)關(guān)注我關(guān)于設(shè)計(jì)模式的文章)。先看看在junit.framework里面使用的設(shè)計(jì)模式。
命令模式:作為輔助單元測(cè)試的框架,開(kāi)發(fā)人員在使用它的時(shí)候,應(yīng)該僅僅關(guān)心測(cè)試用例的編寫(xiě),JUnit只是一個(gè)測(cè)試用例的執(zhí)行器和結(jié)果查看器,不應(yīng)該關(guān)心太多關(guān)于這個(gè)框架的細(xì)節(jié)。而對(duì)于JUnit來(lái)說(shuō),它并不需要知道請(qǐng)求TestCase的操作信息,僅把它當(dāng)作一種命令來(lái)執(zhí)行,然后把執(zhí)行測(cè)試結(jié)果發(fā)給開(kāi)發(fā)人員。命令模式正是為了達(dá)到這種送耦合的目的。
組合模式:當(dāng)系統(tǒng)的測(cè)試用例慢慢變得多起來(lái),挨個(gè)運(yùn)行測(cè)試用例成了一個(gè)棘手的問(wèn)題。作為一個(gè)方便使用的單元測(cè)試框架,這一點(diǎn)是必須解決的。因此JUnit里面提供了TestSuite的功能,它允許將多個(gè)測(cè)試用例放到一個(gè)TestSuite里面來(lái)一次執(zhí)行;而且要進(jìn)一步的支持TestSuite里面套TestSuite的功能。使用組合模式能夠很好的解決這個(gè)問(wèn)題。
在上面我們已經(jīng)提到了junit.extentions包中的內(nèi)容TestSetup。來(lái)看看整個(gè)包的結(jié)構(gòu)吧。
先簡(jiǎn)要的介紹下包中各個(gè)類(lèi)的功能。ActiveTestSuite對(duì)TestSuite進(jìn)行了改進(jìn),使得每個(gè)test運(yùn)行在一個(gè)單獨(dú)的線程里面,并且只到所有的線程都結(jié)束了才會(huì)結(jié)束整個(gè)測(cè)試。ExceptionTestCase是對(duì)TestCase進(jìn)行的改進(jìn),可以方便的判斷測(cè)試類(lèi)是否拋出了期望的異常。而剩下的三個(gè)類(lèi),大概你看的出來(lái)是使用了裝飾模式來(lái)設(shè)計(jì)的。其中TestDecorator為具體裝飾類(lèi)制定好了使用規(guī)則,RepeatedTest和TestSetup則是具體實(shí)現(xiàn)的裝飾類(lèi)。
那為什么extentions包中ActiveTestSuite和ExceptionTestCase沒(méi)有使用裝飾模式呢?原因在于裝飾模式在結(jié)構(gòu)上要求存在類(lèi)似于組合模式的遞歸。而對(duì)于已有的TestCase和TestSuite來(lái)說(shuō),直接繼承它們要比構(gòu)建一個(gè)新的遞歸結(jié)構(gòu)要來(lái)得快得多而且簡(jiǎn)單;并且這些增強(qiáng)功能都只是針對(duì)TestCase或者TestSuite。使用了裝飾模式來(lái)擴(kuò)展的類(lèi)與以上不同的是,它們功能的增強(qiáng)是針對(duì)任何Test實(shí)現(xiàn)的。如果不采用裝飾模式同樣的功能要為T(mén)estCase、TestSuite以及以后的其他Test實(shí)現(xiàn)分別寫(xiě)出子類(lèi)。因此使用裝飾模式能夠很巧妙的解決這個(gè)問(wèn)題。
下面來(lái)介紹下junit.runner包。上面已經(jīng)提到,對(duì)于JUnit使用者來(lái)說(shuō),它可說(shuō)是完全透明的,這個(gè)包里面提供了JUnit自己的測(cè)試類(lèi)加載。下面是包中所有類(lèi)的關(guān)系圖。
沒(méi)有什么好講的,都是使用反射機(jī)制來(lái)將測(cè)試類(lèi)加載進(jìn)來(lái),還有讀取properties文件的操作。如果想學(xué)習(xí)下反射機(jī)制的應(yīng)用可以閱讀這部分的源碼。
剩下的三個(gè)包這里也不作介紹,大部分的內(nèi)容都是GUI的繪制(當(dāng)然junit.textui包除外)。
JUnit中還使用了觀察者模式來(lái)完成單元測(cè)試結(jié)果的自動(dòng)更新(詳細(xì)內(nèi)容請(qǐng)見(jiàn)我關(guān)于觀察者模式的文章)。
這樣,對(duì)JUnit的整體框架有了全面的認(rèn)識(shí)?傮w來(lái)說(shuō)各個(gè)包分工明確,設(shè)計(jì)上采用了必要的設(shè)計(jì)模式來(lái)增強(qiáng)了擴(kuò)展性和重用性,很值得學(xué)習(xí)和借鑒。
三、微觀——執(zhí)行流程與代碼風(fēng)格
來(lái)過(guò)一遍JUnit的執(zhí)行流程吧,這樣你能對(duì)JUnit有個(gè)清晰的認(rèn)識(shí),雖然作為一個(gè)使用者這完全是不必要的。從《JUnit in Action》直接拿來(lái)一張JUnit流程圖。
哦,也許你看暈了,我來(lái)當(dāng)下導(dǎo)游好了。上面已經(jīng)提到了TestRunner是BaseTestRunner的子類(lèi),在三個(gè)不同的ui包中各有一個(gè)TestRunner。這里我們僅以junit.textui包中的為例。
TestRunner作為入口程序是怎么被啟動(dòng)的呢?習(xí)慣了使用容器的我們現(xiàn)在也許很少考慮這個(gè)問(wèn)題。那我們?cè)赥estRunner類(lèi)里面找找吧,你看,你發(fā)現(xiàn)了這個(gè):
public static void main(String args[])
這不是我們寫(xiě)小桌面程序時(shí)經(jīng)常打交道的main方法么?對(duì),這么簡(jiǎn)單。