爲了獲得對系統測試的信心,需要運行多個測試用例。通過使用Command模式,JUnit能夠方便的運行一個單獨的測試用例之後產生測試結果。可是在實際的測試過程中,需要把多個測試用例進行組合成爲一個複合的測試用例,當作一個請求發送給JUnit.這樣JUnit就會面臨一個問題,必須考慮測試請求的類型,是一個單一的TestCase還是一個複合的TestCase,甚至要區分到底有多少個TestCase。這樣Junit框架就要完成像下面這樣的代碼:
if(isSingleTestCase(objectRequest)){
//如果是單個的TestCase,執行run,獲得測試結果
(TestCase)objectRequest.run()
}else if(isCompositeTestCase(objectRequest)){
//如果是一個複合TestCase,就要執行不同的操作,然後進行復雜的算法進行分
//解,之後再運行每一個TestCase,最後獲得測試結果,同時又要考慮
//如果中間測試出現錯誤怎麼辦????、
…………………………
…………………………
}
這會使JUnit必須考慮區分請求(TestCase)的類型(是單個testCase還是複合testCase),而實際上大多數情況下,測試人員認爲這兩者是一樣的。對於這兩者的區別使用,又會使測試用例的編寫變得更加複雜,難以維護和擴展。於是要考慮,怎樣設計JUnit纔可以實現不需要區分單個TestCase還是複合TestCase,把它們統一成相同的請求?
當JUnit不必區分其運行的是一個或多個測試用例時,能夠輕鬆地解決這個問題的模式就是Composite(組合)模式。摘引其意圖,"將對象組合成樹形結構以表示'部分-整體'的層次結構。Composite使得用戶對單個對象和組合對象的使用具有一致性。"在這裏'部分-整體'的層次結構是解決問題的關鍵,可以把單個的TestCase看作部分,而把複合的TestCase看作整體(稱爲TestSuit)。這樣使用該模式便可以恰到好處得解決了這個難題。
組合模式的構成:
1、Component:這是一個抽象角色,它給參加組合的對象規定一個接口。這個角色給出共有的接口和默認的行爲。其實就我們的Test接口,它定義出run方法
2、Composite:實現共有接口並維護一個測試用例的集合,它就是複合測試用例TestSuite
3、Leaf:代表參加組合的對象,它沒有下級子對象,僅定義出參加組合的原始對象的行爲,其實就是單一的測試用例TestCase,它僅實現Test接口的方法
其實componsite模式根據所實現的接口類型區分爲兩種形式,分別稱爲安全式和透明式。JUnit中使用了安全式的結構,這樣在TestCase中沒有管理子對象的方法。
組合模式的代碼實現:
Component:
public interface Component {
public void doSomething();
}
Composite:
public class Composite implements Component {
private List<Component> list = new ArrayList<Component>();
public void add(Component component) {
list.add(component);
}
public void remove(Component component) {
list.remove(component);
}
public List<Component> getAll() {
return list;
}
public void doSomething() {
for (Component com : list) {
com.doSomething();
}
}
}
Leaf:
public class Leaf implements Component {
public void doSomething() {
System.out.println("dosomething");
}
}
Client:
public class Client {
public static void main(String[] args) {
Component com = new Leaf();
Component com2 = new Leaf();
Composite composite = new Composite();
composite.add(com);
composite.add(com2);
composite.doSomething();
}
}
composite模式告訴我們要引入一個Component抽象類,爲Leaf對象和composite對象定義公共的接口。這個類的基本意圖就是定義一個接口。在Java中使用Composite模式時,優先考慮使用接口,而非抽象類,因此引入一個Test接口。當然我們的leaf就是TestCase了。其源代碼如下:
public interface Test {
public abstract void run(TestResult result);
}
public abstract class TestCase extends Assert implements Test {
public void run(TestResult result) {
result.run(this);}
}
下面,列出Composite源碼。將其取名爲TestSuit類。TestSuit有一個屬性fTests (Vector類型)中保存了其子測試用例,提供addTest方法來實現增加子對象TestCase ,並且還提供testCount 和tests 等方法來操作子對象。最後通過run()方法實現對其子對象進行委託(delegate),最後還提供addTestSuite方法實現遞歸,構造成樹形。
public class TestSuite implements Test {
private Vector fTests= new Vector(10);
public void addTest(Test test) {
fTests.addElement(test);
}
public void addTestSuite(Class testClass) {
addTest(new TestSuite(testClass));
}
public void run(TestResult result) {
for (Enumeration e= tests(); e.hasMoreElements(); ) {
if (result.shouldStop() )
break;
Test test= (Test)e.nextElement();
runTest(test, result);
}
}
public Enumeration tests() {
return fTests.elements();
}}
注意所有上面的代碼是對Test接口進行實現的。由於TestCase和TestSuit兩者都符合Test接口,我們可以通過addTestSuite遞歸地將TestSuite再組合成TestSuite,這樣將構成樹形結構。所有開發者都能夠創建他們自己的TestSuit。測試人員可創建一個組合了這些測試用例的TestSuit來運行它們所有的TestCase。
public static Test suite() {
TestSuite suite1 = new TestSuite("我的測試TestSuit1");
TestSuite suite2 = new TestSuite("我的測試TestSuit2");
suite1.addTestSuite(untitled6.Testmath.class);
suite2.addTestSuite(untitled6.Testmulti.class);
suite1.addTest(suite2);
return suite1;
}
效果:
我們來考慮經過使用Composite模式後給系統的架構帶來了那些效果:
1、 簡化了JUnit的代碼 JUnit可以統一處理組合結構TestSuite和單個對象TestCase。使JUnit開發變得簡單容易,因爲不需要區分部分和整體的區別,不需要寫一些充斥着if else的選擇語句。
2、定義了TestCase對象和TestSuite的類層次結構基本對象TestCase可以被組合成更復雜的組合對象TestSuite,而這些組合對象又可以被組合,如上個例子,這樣不斷地遞歸下去。在程序的代碼中,任何使用基本對象的地方都可方便的使用組合對象,大大簡化系統維護和開發。
3、使得更容易增加新的類型的TestCase,如下面介紹的Decorate模式來擴展TestCase的功能
其實junit3.8中用到的組合模式是安全式:
添加Component對象的操作定義在Composite角色中,這樣的話Leaf就無需實現這些方法(因爲Leaf本身根本不需要實現這些方法)
還有一種的透明式:
添加Component對象的操作定義在Component角色中,這樣的話不僅Composite需要實現這些方法,Leaf也需要實現這些方法, 而這些方法對於Leaf來說沒有任何意義,不過將系統實現統一起來了,因此對用戶來說透明(用戶無需區分Composite還是Leaf),因爲這些角色中都具備這些方法。
透明式的代碼實現如下:
Component:
public interface Component {
public void doSomething();
public void add(Component component);
public void remove(Component component) ;
public List<Component> getAll() ;
}
Composite:
public class Composite implements Component {
private List<Component> list = new ArrayList<Component>();
public void add(Component component) {
list.add(component);
}
public void remove(Component component) {
list.remove(component);
}
public List<Component> getAll() {
return list;
}
public void doSomething() {
for (Component com : list) {
com.doSomething();
}
}
}
Leaf:
public class Leaf implements Component {
public void doSomething() {
System.out.println("dosomething");
}
public void add(Component component) {
}
public List<Component> getAll() {
return null;
}
public void remove(Component component) {
}
}
Client:
public class Client {
public static void main(String[] args) {
Component com = new Leaf();
Component com2 = new Leaf();
Component component = new Composite();
component.add(com);
component.add(com2);
component.doSomething();
}
}