java测试工具JUnit_2

按照框架规定:编写的所有测试类,必须继承自junit.framework.TestCase类;里面的测试方法,命名应该以Test开头,必须是public void 而且不能有参数;而且为了测试查错方便,尽量一个TestXXX方法对一个功能单一的方法进行测试;使用assertEqualsjunit.framework.TestCase中的断言方法来判断测试结果正确与否。

       你可以对比着上面测试类中的实现来体会下规定——很简单!而且你在这个测试类中有加入多少个测试方法,就会运行多少个测试方法。

 

四、向前一步

学完了HelloWorld,你已经可以编写标准的单元测试用例了。但是还有一些细节,这里还要说明一下。不要急,很快的!

你在看上面的代码的时候,是不是注意到每个TestXXX方法中都有一条SampleCalculator初始化语句?这很明显不符合编码规范。你可能正要将它提取出来放到构造函数里面去。且慢!在JUnit中的初始化是建议在Setup方法中作的。JUnit提供了一对方法,一个在运行测试方法前初始化一些必备条件而另一个就是测试完毕后去掉初始化的条件(见下图)。

 

另外你是否注意到,上面弹出窗口的一个细节,在绿条下面有ErrorsFailures统计。这两者有何区别呢?

 

       Failures作为单元测试所期望发生的错误,它预示你的代码有bug,不过也可能是你的单元测试代码有逻辑错误(注意是逻辑错误)。Errors不是你所期待的,发生了Error你可以按照下面的顺序来检查:

       检查测试所需的环境,如:数据库连接

       检查单元测试代码

       检查你的系统代码

 

五、成批运行test case

这是前面提到的JUnit特性之一。它方便于系统单元测试的成批运行。使用起来也是非常简单,先看下使用代码:

import junit.framework.Test;

import junit.framework.TestSuite;

public class TestAll{

    public static Test suite(){

        TestSuite suite = new TestSuite("TestSuite Test");

        suite.addTestSuite( TestSample.class);

        return suite;

    }

}

       这个测试程序的编译、运行,和上面TestSample的方式是一样的。

javac -classpath .;junit.jar TestAll.java

java -classpath .;junit.jar junit.swingui.TestRunner TestAll

怎么样?这样你在suite方法里面添加几个TestCase就会运行几个,而且它也可以添加TestSuite来将小一点的集合加入大的集合中来,方便了对于不断增加的TestCase的管理和维护

呵呵,你觉得suite方法的作用是不是于java应用程序的main很类似?并且这里的suite必须严格遵守上面的写法!

 

六、TestRunner

JUnit中已经给出了三种方式表示的TestRunner。你可以分别运行体验下他们的不同。

junit.swingui.TestRunner

junit.awtui.TestRunner

junit.textui.TestRunner

TestCase提供的assert方法会触发一个AssertionFailedError。JUnit针对不同的目的提供一组assert方法。下面只是最简单的一个:

protected void assert(boolean condition) { 
if (!condition) 
throw new AssertionFailedError(); 
}(【译者注】由于与JDK中的关键字assert冲突,在最新的JUnit发布版本中此处的assert已经改为assertTrue。)

  AssertionFailedError不应该由客户(TestCase中的一个测试方法)来负责捕获,而应该由Template Method内部的TestCase.run()来负责。因此我们将AssertionFailedError派生自Error。

public class AssertionFailedError extends Error { 
 public AssertionFailedError () {} 
}

  在TestResult中收集错误的方法可如下所示:

public synchronized void addError(Test test, Throwable t) { 
 fErrors.addElement(new TestFailure(test, t)); 

public synchronized void addFailure(Test test, Throwable t) { 
 fFailures.addElement(new TestFailure(test, t)); 
}

  TestFailure是一个小的框架内部帮助类(helper class),其将失败的测试和为后续报告发送信号的异常绑定在一起。

public class TestFailure extends Object { 
 protected Test fFailedTest; 
 protected Throwable fThrownException; 
}

  规范形式的Collecting parameter模式要求我们将Collecting parameter传递给每一个方法。如果我们遵循该建议,每一个测试方法都将需要TestResult的参数。其将会造成这些方法签名(signature)的“污染”。使用异常来发送失败可以作为一个友善的副作用,使我们能够避免这种签名的污染。一个测试案例方法,或一个其所调用的帮助方法(helper method),可在不必知道TestResult的情况下抛出一个异常。作为一个进修材料,这里给出一个简单的测试方法,其来自于我们MoneyTest套件(【译者注】请参见JUnit发布版本中附带的另外一篇文章JUnit Test Infected: Programmers Love Writing Tests)。其演示了一个测试方法是如何不必知道任何关于TestResult的信息的。

public void testMoneyEquals() { 
 assert(!f12CHF.equals(null)); 
 assertEquals(f12CHF, f12CHF); 
 assertEquals(f12CHF, new Money(12, "CHF")); 
 assert(!f12CHF.equals(f14CHF)); 
}(【译者注】由于与JDK中的关键字assert冲突,在最新的JUnit发布版本中此处的assert已经改为assertTrue。)

  JUnit提出了关于TestResult的不同实现。其缺省实现是对失败和错误的数目进行计数并收集结果。TextTestResult收集结果并以一种文本的形式来表达它们。最后,JUnit Test Runner的图形版本则使用UITestResult来更新图形化的测试状态。

  TestResult是框架的一个扩展点(extension point)。客户能够自定义它们的TestResult类,例如HTMLTestResult可将结果上报为一个HTML文档。
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异常。

 

 

你可能感兴趣的:(java测试工具JUnit_2)