閱讀530 返回首頁    go 阿裏雲 go 技術社區[雲棲]


JUnit源碼分析(一)——Command模式和Composite模式

JUnit的源碼相比於spring和hibernate來說比較簡單,但麻雀雖小,五髒俱全,其中用到了比較多的設計模式。很多人已經在網上分享了他們對JUnit源碼解讀心得,我這篇小文談不出什麼新意,本來不打算寫,可最近工作上暫時無事可做,那就寫寫吧,結合《設計模式》來看看。
    我讀的是JUnit3.0的源碼,目前JUnit已經發布到4.0版本了,盡管有比較大的改進,但基本的骨架不變,讀3.0是為了抓住重點,省去對旁支末節的關注。我們來看看JUnit的核心代碼,也就是Junit.framework包,除了4個輔助類(Assert,AssertFailedError,Protectable,TestFailure),剩下的就是我們需要重點關注的了。我先展示一張UML類圖:

    我們先不去關注TestDecorator類(此處是Decorator模式,下篇文章再講),看看Test接口,以及它的兩個實現類TestCase和TestSuite。很明顯,此處用到了Command模式,為什麼要使用這個模式呢?讓我們先來看看什麼是Command模式。

Command模式

Command模式是行為型模式之一

1.意圖:將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及支持可撤銷的操作。
2.適用場景:
1)抽象出待執行的動作以參數化對象,Command模式是回調函數的麵向對象版本。回調函數,我想大家都明白,函數在某處注冊,然後在稍後的某個時候被調用。
2)可以在不同的時刻指定、排列和執行請求。
3)支持修改日誌,當係統崩潰時,這些修改可以被重做一遍。
4)通過Command模式,你可以通過一個公共接口調用所有的事務,並且也易於添加新的事務。


3。UML圖:
   

4.效果:
1)命令模式將調用操作的對象與如何實現該操作的對象解耦。
2)將命令當成一個頭等對象,它們可以像一般對象那樣進行操縱和擴展
3)可以將多個命令複合成一個命令,與Composite模式結合使用
4)增加新的命令很容易,隔離對現有類的影響
5)可以與備忘錄模式配合,實現撤銷功能。

    在了解了Command模式之後,那我們來看JUnit的源碼,Test接口就是命令的抽象接口,而TestCase和TestSuite是具體的命令
//抽象命令接口
package junit.framework;

/**
 * A <em>Test</em> can be run and collect its results.
 *
 * 
@see TestResult
 
*/
public interface Test {

    
/**
     * Counts the number of test cases that will be run by this test.
     
*/
    
public abstract int countTestCases();
    
/**
     * Runs a test and collects its result in a TestResult instance.
     
*/
    
public abstract void run(TestResult result);
}

//具體命令一

public abstract class TestCase extends Assert implements Test {
    
/**
     * the name of the test case
     
*/
    
private final String fName;
    
/**
    dot.gifdot.gif

//具體命令二

public class TestSuite implements Test {
     dot.gif

由此帶來的好處:
1.客戶無需使用任何條件語句去判斷測試的類型,可以用統一的方式調用測試和測試套件,解除了客戶與具體測試子類的耦合
2.如果要增加新的TestCase也很容易,實現Test接口即可,不會影響到其他類。
3.很明顯,TestSuite是通過組合多個TestCase的複合命令,這裏使用到了Composite模式(組合)
4.盡管未實現redo和undo操作,但將來也很容易加入並實現。

    我們上麵說到TestSuite組合了多個TestCase,應用到了Composite模式,那什麼是Composite模式呢?具體來了解下。

Composite模式

composite模式是對象結構型模式之一。
1.意圖:將對象組合成樹形結構以表示“部分——整體”的層次結構。使得用戶對單個對象和組合結構的使用具有一致性。

2.適用場景:
1)想表示對象的部分-整體層次
2)希望用戶能夠統一地使用組合結構和單個對象。具體到JUnit源碼,我們是希望用戶能夠統一地方式使用TestCase和TestSuite

3.UML圖:

      

圖中單個對象就是樹葉(Leaf),而組合結構就是Compoiste,它維護了一個Leaf的集合。而Component是一個抽象角色,給出了共有接口和默認行為,也就是JUnit源碼中的Test接口。

4.效果:
1)定義了基本對象和組合對象的類層次結構,通過遞歸可以產生更複雜的組合對象
2)簡化了客戶代碼,客戶可以使用一致的方式對待單個對象和組合結構
3)添加新的組件變的很容易。但這個會帶來一個問題,你無法限製組件中的組件,隻能靠運行時的檢查來施加必要的約束條件

    具體到JUnit源碼,單個對象就是TestCase,而複合結構就是TestSuite,Test是抽象角色隻有一個run方法。TestSuite維護了一個TestCase對象的集合fTests:

     private Vector fTests= new Vector(10); 
      
/**
     * Adds a test to the suite.
     
*/
    
public void addTest(Test test) {
        fTests.addElement(test);
    }
    /**
     * Runs the tests and collects their result in a TestResult.
     
*/
    
public void run(TestResult result) {
        
for (Enumeration e= tests(); e.hasMoreElements(); ) {
              
if (result.shouldStop() )
                  
break;
            Test test
= (Test)e.nextElement();
            test.run(result);
        }
    }


當執行run方法時遍曆這個集合,調用裏麵每個TestCase對象的run()方法,從而執行測試。我們使用的時候僅僅需要把TestCase添加到集合內,然後用一致的方式(run方法)調用他們進行測試。

考慮使用Composite模式之後帶來的好處:
1)JUnit可以統一地處理組合結構TestSuite和單個對象TestCase,避免了條件判斷,並且可以遞歸產生更複雜的測試對象
2)很容易增加新的TestCase。


參考資料:《設計模式——可複用麵向對象軟件的基礎》
          《JUnit設計模式分析》 劉兵
          JUnit源碼和文檔


文章轉自莊周夢蝶  ,原文發布時間5.17

最後更新:2017-05-17 13:34:56

  上一篇:go  JUnit源碼分析(二)——觀察者模式
  下一篇:go  org.springframework.core.io包的類圖