Sung in Blog

                     一些技術(shù)文章 & 一些生活雜碎
          一、引子



          JUnit源碼是我仔細(xì)閱讀過的第一個(gè)開源項(xiàng)目源碼。閱讀高手寫的代碼能學(xué)到一些好的編程風(fēng)格和實(shí)現(xiàn)思路,這是提高自己編程水平行之有效的方法,因此早就想看看這些赫赫有名的框架是怎么回事了。今天就拿最簡(jiǎn)單的JUnit下手,也算開始自己的源碼分析之路。

          JUnit作為最著名的單元測(cè)試框架,由兩位業(yè)界有名人士協(xié)力完成,已經(jīng)經(jīng)歷了多次版本升級(jí)(了解JUnit基礎(chǔ)、JUnit實(shí)踐)。JUnit總體來說短小而精悍,有不少值得我們借鑒的經(jīng)驗(yàn)在里面;但是也有一些不足存在,當(dāng)然這對(duì)于任何程序來說都是難免的。


          下面我們將從整體(宏觀)和細(xì)節(jié)(微觀)兩方面來分析JUnit源碼,以下分析基于3.8.1版。


          二、宏觀——架構(gòu)與模式



          打開源碼文件,你會(huì)發(fā)現(xiàn)JUnit源碼被分配到6個(gè)包中:junit.awtui、junit.swingui、junit.textui、junit.extensions、junit.framework、junit.runner。其中前三個(gè)包中包含了JUnit運(yùn)行時(shí)的入口程序以及運(yùn)行結(jié)果顯示界面,它們對(duì)于JUnit使用者來說基本是透明的。junit.runner包中包含了支持單元測(cè)試運(yùn)行的一些基礎(chǔ)類以及自己的類加載器,它對(duì)于JUnit使用者來說是完全透明的。



          剩下的兩個(gè)包是和使用JUnit進(jìn)行單元測(cè)試緊密聯(lián)系在一起的。其中junit.framework包含有編寫一般JUnit單元測(cè)試類必須是用到的JUnit類;而junit.extensions則是對(duì)framework包在功能上的一些必要擴(kuò)展以及為更多的功能擴(kuò)展留下的接口。



          JUnit提倡單元測(cè)試的簡(jiǎn)單化和自動(dòng)化。這就要求JUnit的使用要簡(jiǎn)單化,而且要很容易的實(shí)現(xiàn)自動(dòng)化測(cè)試。整個(gè)JUnit的設(shè)計(jì)大概也是遵循這個(gè)前提吧。整個(gè)框架的骨干僅有三個(gè)類組成(下圖所示)。
          image

          ?????? 如果你掌握了TestCase、TestSuite、BaseTestRunner的工作方式,那么你就可以隨心所欲的編寫測(cè)試代碼了。



          ?????? 下面我們來看看junit.framework中類之間的關(guān)系,下圖是我根據(jù)源代碼分析出來的,大部分關(guān)系都表示了出來。

          image

          先來看看各個(gè)類的職責(zé)。Assert類提供了JUnit使用的一整套的斷言,這套斷言都被TestCase繼承下來,Assert也就變成了透明的。Test接口是為了統(tǒng)一TestCase和TestSuite的類型;而TestCase里面提供了運(yùn)行單元測(cè)試類的方法;在TestSuite中則提供了加載單元測(cè)試類,檢驗(yàn)測(cè)試類格式等等的方法。TestResult故名思意就是提供存放測(cè)試結(jié)果的地方,但是在JUnit中它還帶有一點(diǎn)控制器的功能。


          在這里指出其中我認(rèn)為有些不妥的地方。圖上TestCase和TestResult之間是雙向的依賴關(guān)系,而在UML類圖的關(guān)系中指出:依賴關(guān)系總是單向的。就讓我們來看看這這個(gè)可疑的地方。


          TestCase中的代碼:

          /**

          * Runs the test case and collects the results in TestResult.
          */

          public void run(TestResult result) {
          //調(diào)用了result中的run方法,
          //TestResult按照名稱來看應(yīng)該是一個(gè)記錄測(cè)試結(jié)果的類,怎么還能run?
          ?????? result.run(this);

          }

          相應(yīng)得TestResult中的代碼:

          /**
          * Runs a TestCase.
          */
          protected void run(final TestCase test) {

          ?????? //開始測(cè)試
          ?????? startTest(test);

          ?????? //這個(gè)匿名內(nèi)類的使用一會(huì)再講

          ?????? Protectable p= new Protectable() {
          ??????????????public void protect() throws Throwable {

          ????????????????????//天那,這里又調(diào)用了TestCase里面的runBare方法

          ????????????????????test.runBare();

          ???????????? }

          ??????};

          ?????? runProtected(test, p); //這個(gè)方法就是要執(zhí)行上面制定的匿名內(nèi)類
          ?????? endTest(test);



          }




          TestResult中runProtected方法:



          public void runProtected(final Test test, Protectable p) {



          ?????? try {



          ??????????????p.protect();



          ?????? }



          ?????? catch (AssertionFailedError e) {



          ??????????????addFailure(test, e);??????????????//給TestResult添加失敗記錄



          ?????? }



          ?????? catch (ThreadDeath e) { // don't catch ThreadDeath by accident



          ??????????????throw e;



          ?????? }



          ?????? catch (Throwable e) {



          ??????????????addError(test, e);????????//給TestResult添加出錯(cuò)記錄



          ?????? }



          }




          為什么JUnit里面會(huì)出現(xiàn)這樣奇怪的依賴關(guān)系,還有違反單一職責(zé)原則的TestResult?當(dāng)我看到j(luò)unit.extentions包中的TestSetup時(shí),也許我猜到了作者的用意。我們來看下TestSetup中有關(guān)的代碼:



          public void run(final TestResult result) {



          ?????? //又看到了上面類似的匿名內(nèi)部類



          ?????? Protectable p= new Protectable() {



          ??????????????public void protect() throws Exception {



          ???????????????????? //不過這個(gè)內(nèi)部類里面的實(shí)現(xiàn)有所不同



          setUp();



          ???????????????????? basicRun(result);



          ???????????????????? tearDown();



          ??????????????}



          ?????? };



          ?????? //調(diào)用了TestResult中的runProtected方法來執(zhí)行上面的實(shí)現(xiàn)



          ?????? result.runProtected(this, p);



          }




          這個(gè)類的產(chǎn)生是為了彌補(bǔ)TestCase類的一個(gè)小小的缺陷(具體請(qǐng)見下部分)。注意到在這個(gè)類里面也有和TestResult類似的匿名內(nèi)部類。這種匿名內(nèi)部類全是Protected接口的無名實(shí)現(xiàn),這里的目的我認(rèn)為有兩點(diǎn):



          1)????????由于內(nèi)部類可以在接下來的情景中完全不可見,而且不被任何人使用,因此也就隱藏了接口的實(shí)現(xiàn)細(xì)節(jié)。



          2)????????為了提高可重用性,而使用內(nèi)部類比較快捷。這樣不管你protect方法里面具體執(zhí)行什么,對(duì)它錯(cuò)誤、失敗、異常捕捉的代碼(TestResult中的runProtected方法)就可以重用了。



          這也正是為什么會(huì)出現(xiàn)上面那樣奇怪的依賴關(guān)系:為了復(fù)用,就要讓runProtected方法放在一個(gè)TestCase和TestSetup都能調(diào)用的地方。



          不過我認(rèn)為為了復(fù)用而破壞了系統(tǒng)良好的結(jié)構(gòu)和可讀性,是需要仔細(xì)斟酌的。JUnit這樣的設(shè)計(jì)估計(jì)是為了以后框架多次擴(kuò)展后的重用考慮的。



          說完了讓我費(fèi)解的問題。談?wù)勎矣X得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è)試的框架,開發(fā)人員在使用它的時(shí)候,應(yīng)該僅僅關(guān)心測(cè)試用例的編寫,JUnit只是一個(gè)測(cè)試用例的執(zhí)行器和結(jié)果查看器,不應(yīng)該關(guān)心太多關(guān)于這個(gè)框架的細(xì)節(jié)。而對(duì)于JUnit來說,它并不需要知道請(qǐng)求TestCase的操作信息,僅把它當(dāng)作一種命令來執(zhí)行,然后把執(zhí)行測(cè)試結(jié)果發(fā)給開發(fā)人員。命令模式正是為了達(dá)到這種送耦合的目的。



          ?????? 組合模式:當(dāng)系統(tǒng)的測(cè)試用例慢慢變得多起來,挨個(gè)運(yùn)行測(cè)試用例就成了一個(gè)棘手的問題。作為一個(gè)方便使用的單元測(cè)試框架,這一點(diǎn)是必須解決的。因此JUnit里面提供了TestSuite的功能,它允許將多個(gè)測(cè)試用例放到一個(gè)TestSuite里面來一次執(zhí)行;而且要進(jìn)一步的支持TestSuite里面套TestSuite的功能。使用組合模式能夠很好的解決這個(gè)問題。
          在上面我們已經(jīng)提到了junit.extentions包中的內(nèi)容TestSetup。來看看整個(gè)包的結(jié)構(gòu)吧。image


          先簡(jiǎn)要的介紹下包中各個(gè)類的功能。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è)試類是否拋出了期望的異常。而剩下的三個(gè)類,大概你看的出來是使用了裝飾模式來設(shè)計(jì)的。其中TestDecorator為具體裝飾類制定好了使用規(guī)則,RepeatedTest和TestSetup則是具體實(shí)現(xiàn)的裝飾類。



          那為什么extentions包中ActiveTestSuite和ExceptionTestCase沒有使用裝飾模式呢?原因在于裝飾模式在結(jié)構(gòu)上要求存在類似于組合模式的遞歸。而對(duì)于已有的TestCase和TestSuite來說,直接繼承它們要比構(gòu)建一個(gè)新的遞歸結(jié)構(gòu)要來得快得多而且簡(jiǎn)單;并且這些增強(qiáng)功能都只是針對(duì)TestCase或者TestSuite。使用了裝飾模式來擴(kuò)展的類與以上不同的是,它們功能的增強(qiáng)是針對(duì)任何Test實(shí)現(xiàn)的。如果不采用裝飾模式同樣的功能要為TestCase、TestSuite以及以后的其他Test實(shí)現(xiàn)分別寫出子類。因此使用裝飾模式能夠很巧妙的解決這個(gè)問題。

          下面來介紹下junit.runner包。上面已經(jīng)提到,對(duì)于JUnit使用者來說,它可說是完全透明的,這個(gè)包里面提供了JUnit自己的測(cè)試類加載。下面就是包中所有類的關(guān)系圖。


          image

          沒有什么好講的,都是使用反射機(jī)制來將測(cè)試類加載進(jìn)來,還有讀取properties文件的操作。如果想學(xué)習(xí)下反射機(jī)制的應(yīng)用可以閱讀這部分的源碼。

          剩下的三個(gè)包這里也不作介紹,大部分的內(nèi)容都是GUI的繪制(當(dāng)然junit.textui包除外)。

          JUnit中還使用了觀察者模式來完成單元測(cè)試結(jié)果的自動(dòng)更新(詳細(xì)內(nèi)容請(qǐng)見我關(guān)于觀察者模式的文章)。

          這樣,對(duì)JUnit的整體框架有了全面的認(rèn)識(shí)。總體來說各個(gè)包分工明確,設(shè)計(jì)上采用了必要的設(shè)計(jì)模式來增強(qiáng)了擴(kuò)展性和重用性,很值得學(xué)習(xí)和借鑒。
          posted on 2005-10-16 13:06 Sung 閱讀(709) 評(píng)論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 唐河县| 南岸区| 布拖县| 湖南省| 阿城市| 舞钢市| 焉耆| 峨眉山市| 鹰潭市| 湟中县| 裕民县| 揭阳市| 新安县| 比如县| 峨山| 柘城县| 朔州市| 景东| 崇阳县| 东丽区| 宾川县| 浙江省| 小金县| 云霄县| 紫阳县| 和龙市| 延长县| 交城县| 陆河县| 麟游县| 鹿泉市| 海林市| 登封市| 惠水县| 资中县| 庆云县| 临桂县| 嘉鱼县| 如皋市| 涿州市| 临洮县|