关闭

JUnit的框架设计及其使用的设计模式

发表于:2009-5-22 11:35

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:未知    来源:网络转载

  这个模式要求我们引入一个抽象类,该类为简单对象和组合对象定义了统一的接口,它的主要作用是定义这个接口,在Java里,我们直接使用接口,没有必要用抽象类来定义接口,因为Java有接口的概念,而象C++没有接口的概念,使用接口避免了将JUnit功能交付给一个特定的基类。所有的测试必须遵从这个接口,因此测试客户所看到的就是这个接口:

  public interface Test {

  public abstract void run(TestResult result);

  }

  Leaf所代表的简单TestCase实现了这个接口,我们前面已经讨论过了。

  下面,我们讨论Composite,即组合测试用例,称为测试套件(TestSuite)。TestSuite用Vector来存放他的孩子(child test):

  public class TestSuite implements Test {

  private Vector fTests= new Vector();

  }

  测试套件的run()方法委托给它的孩子,即依次调用它的孩子的run()方法:

  public void run(TestResult result) {

  for (Enumeration e= fTests.elements(); e.hasMoreElements(); ) {

  Test test= (Test)e.nextElement();

  test.run(result);

  }

  }

图5:测试套件应用了composite模式

  测试客户要向测试套件中添加测试,调用addTest方法:

  public void addTest(Test test) {

  fTests.addElement(test);

  }

  注意,上面的代码是如何依赖于Test接口的。既然TestCase和TestSuite都遵从同一个Test接口,因此测试套件可以递归的包含测试用例和测试套件。开发人员可以创建自己的TestSuite,并用这个套件运行其中所有的测试。

  这是一个创建TestSuite的例子:

  public static Test suite() {

  TestSuite suite= new TestSuite();

  suite.addTest(new MoneyTest("testMoneyEquals"));

  suite.addTest(new MoneyTest("testSimpleAdd"));

  }

  以上代码中,suite.addTest(new MoneyTest("testMoneyEquals"))表示向测试套件suite中添加一个测试,指定测试类为MoneyTest,测试方法为testMoneyEquals(由selector选定该方法,与模板方法runTest对接)。

  在MoneyTest类中没有声明MoneyTest(String)的构造器,那么MoneyTest(“testMoneyequals”)执行时调用super(String)构造器,它定义于MoneyTest的父类TestCase中。

  TestCase(此处也即MoneyTest)把"testMoneyEquals"字符串存放在私有变量中,这个变量是一个方法指针,使用的是Pluggable Selector模式,表明它所指定的方法testMoneyEquals要与模板方法runTest对接。表明该测试用例实例中起作用的是testMoneyEquals(),利用Java的反射特性实现对该方法的调用。

  因此以上代码向suite中添加了2个测试实例,类型均为MoneyTest,但测试方法不同。

  这个例子工作很好,但要我们手工添加所有的测试,这是很笨的办法,当你编写一个测试用例时,你要记得把它们添加到一个静态方法suite()中,否则它就不会运行。为此,我们为TestSuite增加了一个构造器,它用测试用例的类作为其参数,它的作用就是提取这个类中的所有测试方法,并创建一个测试套件,把这些提取出来的测试方法放进所创建的测试套件中。但这些测试方法要遵守一个简单的协定,即方法命名以“test”作为前缀,且不带参数。这个构造器利用这个协定,使用Java的反射特性找出测试方法,并构建测试对象。如果使用这个构造器,上面的代码就很简单:

  public static Test suite() {

  return new TestSuite(MoneyTest.class);

  }

  即为MoneyTest类中中的每一个testXxx方法都创建一个测试实例。但前一种方式仍然有用,比如你只想运行测试用例的一个子集。

  3.6、概要

  JUnit的设计到此告一段落。下图显示了JUnit设计中使用的模式。

图6:JUnit中的模式

  注意TestCase(JUnit框架中的核心功能)参与了4个模式。这说明在这个框架中,TestCase类是“模式密集(pattern density)”的,它是框架的中心,与其它支持角色有很强的关联。

  下面是查看JUnit模式的另外一个视角。在这个情节图中,你依次看到每个模式所带来的效果。

  Command模式创建了TestCase类,Template Method模式创建了run方法,等等。这里所用的符号都来自图6,只是去掉了文字。

图7:JUnit中的模式情节板

  要注意一点,当我们应用Composite模式时,复杂性突然增加了。Composite模式功能很强大,使用当心。

  4、结论

  为了得出结论,我们作一些一般的观察:

  • 模式

  以前,当我们开发框架和试图向其它人解释框架时,我们发现用模式来讨论设计是无用的。现在,你处于一个极好的处境来判断用模式来描述框架是否有效,如果你喜欢上述讨论,那么也用这样的方式来表示你的系统。

  • 模式密集度

  围绕着TestCase有很高的模式密集度,TestCase是JUnit设计中的关键抽象,它易于使用,但难以改变。我们发现围绕关键抽象有很高的模式密集度,是成熟框架的普遍现象。对于不成熟的框架,情形相反,它们模式密集度不高。一旦你发现你要解决的是什么问题,你就开始“浓缩”你的解决方案,达到高的模式密集度。

  • Eat your own dog food

  As soon as we had the base unit testing functionality implemented, we applied it ourselves.A TestTest verifies that the framework reports the correct results for errors, successes,and failures. We found this invaluable as we continued to evolve the design of theframework. We found that the most challenging application of JUnit was testing its ownbehavior.

  • 交集,而非合并

  在框架开发中,总想包含进每一个特性,想让框架尽可能有价值,但有另一个因素作用相反:你希望开发人员使用你的框架。框架的特性越少,学习就越容易,开发人员就越可能使用它。JUnit的设计就是这样的思路,它实现那些对于运行测试而言是必不可少的特性,如运行测试套件、将不同的测试互相隔离、自动运行测试等等。当然我们还会添加新的特性,但我们会仔细地加以选择,并把它们放进JUnit扩展包中。在扩展包中,一个值得注意的成员就是TestDecorator类,它使用了Decorator模式,可以在测试代码运行之前或运行之后执行其它的代码。

  • 框架作者要花很多时间阅读框架代码

  我们阅读框架代码的时间要比编写代码的时间多得多;我们为框架增加功能,但我们花同样多的时间为删除框架中的重复功能。我们用各种途径为框架设计、增加类、移动类职责,只要我们能考虑到的各种途径。在JUnit、测试、对象设计、框架开发和写文章的工作中,我们不断地提高洞察力,并受益无穷。

44/4<1234
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号