为了获得对系统测试的信心,需要运行多个测试用例。通过使用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 list = new ArrayList();
public void add(Component component) {
list.add(component);
}
public void remove(Component component) {
list.remove(component);
}
public List 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 getAll() ;
}
Composite:
public class Composite implements Component {
private List list = new ArrayList();
public void add(Component component) {
list.add(component);
}
public void remove(Component component) {
list.remove(component);
}
public List 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 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();
}
}