庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理
    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 <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);
        }
    }

当执行run方法时遍历这个集合,调用里面每个TestCase对象的run()方法,从而执行测试。我们使用的时候仅仅需要把TestCase添加到集合内,然后用一致的方式(run方法)调用他们进行测试。

考虑使用Composite模式之后带来的好处:
1)JUnit可以统一地处理组合结构TestSuite和单个对象TestCase,避免了条件判断,并且可以递归产生更复杂的测试对象
2)很容易增加新的TestCase。


参考资料:《设计模式——可复用面向对象软件的基础》
          《JUnit设计模式分析》 刘兵
          JUnit源码和文档









    

评论

# re: JUnit源码分析(一)——Command模式和Composite模式  回复  更多评论   

2007-04-05 15:20 by 山风小子
嗯,不错,大有收获,谢谢:)

# re: JUnit源码分析(一)——Command模式和Composite模式  回复  更多评论   

2007-04-05 15:28 by leekiang
你的uml图用什么工具画的?蛮漂亮的

# re: JUnit源码分析(一)——Command模式和Composite模式  回复  更多评论   

2007-04-05 15:39 by dennis
@leekiang

用oracle的JDeveloper,从源代码生成的UML图的,呵呵

只有注册用户登录后才能发表评论。


网站导航: