自动测试:变化多端的输出
 

2009-02-12 来源:网络

 

在自动测试系统中, 经常会遇到输出测试报告的问题。报告内容类型是一定的,大致可以分为:

测试的集合的名称

测试开始的时间

测试结束的时间

测试用例的名称

测试结果

测试数据

测试日志

...

这样看上去,好像并不存在什么问题,因为报告内容类型可以被定义清楚,并且也不存在变化的需求。所以,只需在需要输出测试报告的地方将内容输出就可以了。

但是,现实往往不如人所愿,随着测试系统的扩展,原先我们将这些输出是以本文格式文件的信息存储在本地。而现在为了与另一个测试系统交互,我们必需将输出的形式由文本格式文件转变成为XML格式的输出。这可是个不大不小的麻烦,我们必需修改那些原来“需要输出的地方”的代码。可想而知,这些代码已经被层层的包围在测试逻辑中间了。

这真是一件简单、暴力的活动。谁也不原意做第二次, 不过,放眼望去,“第二次”并不遥远。数据库式的集中存储正向我们招手。输出到数据库已经被提上议程。我们不得不考虑将报告输出的逻辑与测试的逻辑分离开。其实,这个不是什么困难的事,我们只需根据报告内容类型定义一个接口,并实现两种不同的输出方式,文本格式输出和XML格式输出。

  Output class diagram I

在需要有新的输出方式的时候, 我们只需要添加一个新的输出方式的实现。同时,为了能使输出方式的可配置,还需为它设计一个简单工厂。这样,出输格式的扩展性就得到了保证,还可以通过配置文件还选择不同的输出。

  OutputterFactory


 1package my.cnblog.output;
 2
 3/*The Interface of Outputter*/
 4
 5public interface IOutputter {
 6    public boolean StartSuite(String suiteName);
 7    public boolean EndSuite();
 8    public boolean TestResult(String result);
 9    /*
10     * other interfaces
11     */

12}

13
14package my.cnblog.output;
15
16/*one implement of Outputter*/
17
18public class TXTOutputter implements IOutputter {
19
20    @Override
21    public boolean EndSuite() {
22        // TODO Auto-generated method stub
23        return false;
24    }

25
26    @Override
27    public boolean StartSuite(String suiteName) {
28        // TODO Auto-generated method stub
29        return false;
30    }

31
32    @Override
33    public boolean TestResult(String result) {
34        // TODO Auto-generated method stub
35        return false;
36    }

37
38}

39
40



 1package my.cnblog.output;
 2
 3/*The Factory of Outputter*/
 4
 5public class OutputterFactory {
 6    IOutputter Create(String outputter){
 7        if (outputter.equalsIgnoreCase("TXTOutputter")){
 8            return new TXTOutputter();
 9        }

10        else if(outputter.equalsIgnoreCase("XMLOutputter")){
11            /*return new XMLOutputter();*/
12        }

13        return null;
14    }

15
16}

到了目前这个状态,似乎我们的问题得到了解决。我们可以随时的改变我们的输出方式。而没有太多的代价。测试系统也在此基础上如火如荼的开发着。好景不长,很多在此基础上开发的程序员,提出了一个需求,那就是他们的测试系统同时需要有两种或两种以上的输出方式,在传输到数据库里的同时还需要在本地输出一份报告。 这时你会说,你可以在测试系统中创建两种输出方法对象不就行了嘛,然后在“需要输出的地方”调用两次就行了。这个确实是一个不负责任的说法。(经常能见到这样的设计师),对客户程序带来的烦麻也显而易见。

那我们就来看来如何解决这样的问题,首先,总结一下问题描述。在测试系统中,

  • 测试报告具有相同的输出内容类型
  • 测试报告需要有不同的输出形式(方式)
  • 测试报告可能同时需要有两种或两种以上的输出形式
  • 客户程序无需感知有多种输出方式存在

这时,往往有程序员会打开《设计模式》去寻找解决的方案,这真的是不好的方法,或者是找不到现成的模式,或者模式的误用就发生了。好在,这个确实是模式的一种也许你可能找得到,但我还是不建议套用模式的方式解决问题。接下来我们看看通过自己分析来解决问题,和现存模式有什么区别。

根据问题描述4可以看得出,多种输出同时生效对于客户程序是透明的,也就是说,客户程序对于多种输出方式的调用和单个输出方式的调用具有相同的接口,另外,多种输出方式是并列的关系,并不存在层次关系。我们可能根据客户程序调用形式想到,若我们将生成的一个或多个输出对象存储在一个容器内部,并且让这个容器也具用输出内容接口。当对于这个容器调用输出内容接口的时候,就将这个操作依次传递给容器内所用对象。 哦~!原来是这样,就可以解决同时多种输出的问题,并且从客户角度看,只是对一个简单输出对象接口的调用,而完全不知,此时已是复杂对象了。

composite outputter


 1package my.cnblog.output;
 2import java.util.Vector;
 3import java.util.Iterator;
 4
 5/*A Composite Outputter*/
 6
 7public class OutputterContainer implements IOutputter {
 8
 9    private Vector<IOutputter> outputterVector = new Vector<IOutputter>();
10    @Override
11    public boolean EndSuite() {
12        Iterator<IOutputter> it = outputterVector.iterator();
13        while(it.hasNext()){
14            boolean rs = it.next().EndSuite();
15            if (rs == falsereturn false;
16        }

17        return true;
18    }

19
20    @Override
21    public boolean StartSuite(String suiteName) {
22        // the similar with the first function
23        return false;
24    }

25
26    @Override
27    public boolean TestResult(String result) {
28        // the similar with the first function
29        return false;
30    }

31    
32    public void Add(IOutputter outputter){
33        outputterVector.add(outputter);
34    }

35    
36    public void Remove(IOutputter outputter){
37        outputterVector.remove(outputter);
38    }

39
40}



 1package my.cnblog.output;
 2
 3/*Modified Outputter Factory*/
 4
 5public class OutputterFactory {
 6    IOutputter Create(String outputterList){
 7        OutputterContainer oc = new OutputterContainer();
 8        String[] list = outputterList.split(";");
 9        for (int i=0;i<list.length;i++){
10            String outputter = list[i];
11            if (outputter.equalsIgnoreCase("TXTOutputter")){
12                oc.Add(new TXTOutputter());
13            }

14            else if(outputter.equalsIgnoreCase("XMLOutputter")){
15                /*oc.Add(new TXTOutputter());*/
16            }

17        }

18        return null;
19    }

20
21}

22

代码为示意代码,真实代码还有更多细节需要处理

 是的,就这么简单。我们看到系统正常的工作了。也许你会问,那它是什么模式呢?我前面提过它是一种模式来着的。是的。它应该是组合模式(Composite Pattern)的一种退化形式。那为什么说他是退化的形式呢?我们先来看一下,组合模式的定义:

合成(Composite)模型模式属于对象的结构模式[GOF95],有时又叫做部分-整体(Part-Whole)模式。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式可以客户端将单纯元素与复合元素同等看待。

引用自《Java与模式》阎宏

从这个定义可以看出合成模式大致有三个特点,一具用两种对象,简单对象和复杂对象,但两种对象对客户程序具有相同接口,二具有层次结构。三具有递归特性。

从这些条件看来,我们的系统确实简单的多,对于客户程序来说,完全不知道有复杂对象,由于工厂的介入,系统不会有递归的情况出现,并且,对于该系统也没有递归的要求。其实,这个输出系统是组合模式,工厂模式,及宽窄接口方式综合的来解决问题的。若上来就硬套一种模式的话,一定会带来很大的烦麻。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织