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

dennis_zane發表於2007-04-05
    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 Test 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;
    
/**
   

//具體命令二

public class TestSuite implements Test {
     

由此帶來的好處:
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原始碼和文件









    

相關文章