Yesky首页| 产品报价| 行情| 手机 | 数码 | 笔记本 | 台式机 | DIY硬件 | 外设 | 网络 | 数字家庭 | 评测 | 软件 | e时代 | 游戏 | 图片 | 壁纸 | 群乐 | 社区 | 博客 | 下载
软件频道>程序开发>JavaVBVCDelphiC/C++Web开发微软专栏移动数据库程序人生软件工程|Comet程序开发
您现在的位置: 天极网 > 开发频道 > Java开源测试工具JUnit简介
全文

Java开源测试工具JUnit简介

2006-07-11 07:00 作者: 李巍 出处: cn-java 责任编辑:>方舟
  3.4 不愚蠢的子类-再论TestCase

  我们已经应用Command来表现一个测试。Command依赖于一个单独的像execute()这样的方法(在TestCase中称为run())来对其进行调用。这个简单接口允许我们能够通过相同的接口来调用一个command的不同实现。

  我们需要一个接口对我们的测试进行一般性地运行。然而,所有的测试案例都被实现为相同类的不同方法。这避免了不必要的类扩散(proliferation of classes)。一个给定的测试案例类(test case class)可以实现许多不同的方法,每一个方法定义了一个单独的测试案例(test case)。每一个测试案例都有一个描述性的名称,如testMoneyEquals或testMoneyAdd。测试案例并不符合简单的command接口。相同Command类的不同实例需要与不同的方法来被调用。因此我们下面的问题就是,使所有测试案例从测试调用者的角度上看都是相同的。

  回顾当前可用的设计模式所涉及的问题,Adapter(适配器)模式便映入脑海。Adapter具有以下意图“将一个类的接口转换成客户希望的另外一个接口”。这听起来非常适合。Adapter告诉我们不同的这样去做的方式。其中之一便是class adapter(类适配器),其使用子类化来对接口进行适配。例如,为了将testMoneyEquals适配为runTest,我们实现了一个MoneyTest的子类并重写runTest方法来调用testMoneyEquals。

public class TestMoneyEquals extends MoneyTest {
public TestMoneyEquals() { super("testMoneyEquals"); }
protected void runTest () { testMoneyEquals(); }
}

  使用子类化需要我们为每一个测试案例都实现一个子类。这便给测试者放置了一个额外的负担。这有悖于JUnit的目标,即框架应该尽可能地使测试案例的增加变得简单。此外,为每一个测试方法创建一个子类会造成类膨胀(class bloat)。许多类将仅具有一个单独的方法,这种开销不值得,而且很难会提出有意义的名称。

  Java提供了匿名内部类(anonymous inner class),其提供了一个让人感兴趣的Java所专门的方案来解决类的命名问题。通过匿名内部类我们能够创建一个Adapter而不必创造一个类的名称:

TestCase test= new MoneyTest("testMoneyEquals ") {
protected void runTest() { testMoneyEquals(); }
};

  这与完全子类化相比要便捷许多。其是以开发者的一些负担作为代价以保持编译时期的类型检查(compile-time type checking)。Smalltalk Best Practice Pattern描述了另外的方案来解决不同实例的问题,这些实例是在共同的pluggable behavior(插件式行为)标题下的不同表现。该思想是使用一个单独的参数化的类来执行不同的逻辑,而无需进行子类化。

  Pluggable behavior的最简单形式是Pluggable Selector(插件式选择器)。Pluggable Selector在一个实例变量中保存了一个Smalltalk的selector方法。该思想并不局限于Smalltalk,其也适用于Java。在Java中并没有一个selector方法的标记。但是Java reflection(反射) API允许我们可以根据一个方法名称的表示字符串来调用该方法。我们可以使用该种特性来实现一个Java版的pluggable selector。岔开话题而言,我们通常不会在平常的应用程序中使用反射。在我们的案例中,我们正在处理的是一个基础设施框架,因此它可以戴上反射的帽子。

  JUnit可以让客户自行选择,是使用pluggable selector,或是实现上面所提到的匿名adapter类。正因如此,我们提供pluggable selector作为runTest方法的缺省实现。在该情况下,测试案例的名称必须要与一个测试方法的名称相一致。如下所示,我们使用反射来对方法进行调用。首先我们会查找Method对象。一旦我们有了method对象,便会调用它并传递其参数。由于我们的测试方法没有参数,所以我们可以传递一个空的参数数组。

protected void runTest() throws Throwable {
Method runMethod= null;
try {
runMethod= getClass().getMethod(fName, new Class[0]);
} catch (NoSuchMethodException e) {
assert("Method \""+fName+"\" not found", false);
}
try {
runMethod.invoke(this, new Class[0]);
}
// catch InvocationTargetException and IllegalAccessException
}

  JDK1.1的reflection API仅允许我们发现public的方法。基于这个原因,你必须将测试方法声明为public,否则将会得到一个NoSuchMethodException异常。

  在下面的设计快照中,添加进了Adapter和Pluggable Selector。


图4 TestCase应用Adapter(与一个匿名内部类一起)或Pluggable Selector

  3.5 不必关心一个或多个-TestSuit

  为了获得对系统状态的信心,我们需要运行许多测试。到现在为止,JUnit能够运行一个单独的测试案例并在一个TestResult中报告结果。我们接下来的挑战是要对其进行扩展,以使其能够运行许多不同的测试。当测试调用者不必关心其运行的是一个或多个测试案例时,这个问题便能够轻松地解决。能够在该情况下度过难关的一个流行模式就是Composite(组合)。摘引其意图,“将对象组合成树形结构以表示‘部分-整体’的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。”在这里‘部分-整体’的层次结构是让人感兴趣的地方。我们想支持能够层层相套的测试套件。

  Composite引入如下的参与者:

  · Component:声明我们想要使用的接口,来与我们的测试进行交互。

  · Composite:实现该接口并维护一个测试的集合。

  · Leaf:代表composite中的一个测试案例,其符合Component接口。

  该模式告诉我们要引入一个抽象类,来为单独的对象和composite对象定义公共的接口。这个类的基本意图就是定义一个接口。在Java中应用Composite时,我们更倾向于定义一个接口,而非抽象类。使用接口避免了将JUnit提交成一个具体的基类来用于测试。所必需的是这些测试要符合这个接口。因此我们对模式的描述进行变通,并引入一个Test接口:

public interface Test {
public abstract void run(TestResult result);
}

  TestCase对应着Composite中的一个Leaf,并且实现了我们上面所看到的这个接口。

  下面,我们引入参与者Composite。我们将其取名为TestSuit(测试套件)类。TestSuit在一个Vector中保存了其子测试(child test):

public class TestSuite implements Test {
private Vector fTests= new Vector();
}

  run()方法对其子成员进行委托(delegate):

public void run(TestResult result) {
for (Enumeration e= fTests.elements(); e.hasMoreElements(); ) {
Test test= (Test)e.nextElement();
test.run(result);
}
}


图5 TestSuit应用Composite

  最后,客户必须能将测试添加到一个套件中,它们将使用addTest方法来这样做:

public void addTest(Test test) {
fTests.addElement(test);
}

  注意所有上面的代码是如何仅对Test接口进行依赖的。由于TestCase和TestSuit两者都符合Test接口,我们可以递归地将测试套件再组合成套件。所有开发者都能够创建他们自己的TestSuit。我们可创建一个组合了这些套件的TestSuit来运行它们所有的。

  下面是一个创建TestSuit的示例:

public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTest(new MoneyTest("testMoneyEquals"));
suite.addTest(new MoneyTest("testSimpleAdd"));
}

  这会很好地工作,但它需要我们手动地将所有测试添加到一个套件中。早期的JUnit采用者告诉我们这样是愚蠢的。只要你编写一个新的测试案例,你就必须记着要将其添加到一个static的suit()方法中,否则其将不会运行。我们添加了一个TestSuit的便捷构造方法,该构造方法将测试案例类作为一个参数。其意图是提取(extract)测试方法,并创建一个包含这些测试方法的套件。测试方法必须遵循的简单的约定是,以前缀“test”开头且不带参数。便捷构造方法就使用该约定,通过使用反射发现测试方法来构造测试对象。使用该构造方法,以上代码将会简化为:

public static Test suite() {
return new TestSuite(MoneyTest.class);
}

  当你只是想运行测试案例的一个子集时,则最初的方式将依然有用。

共4页。 9 1 2 3 4 :
网友关注
最新上市
编辑推荐
欢迎订阅天极网RSS聚合资讯:http://www.yesky.com/index.xml