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

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;
/**


//具體命令二
public class TestSuite implements Test {
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;
/**


//具體命令二
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);
}
}
/**
* 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