站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Java 专题应用JUnit实施单元测试(节选)

应用JUnit实施单元测试(节选)

摘要: 应用JUnit实施单元测试(节选)

</td> </tr> <tr> <td height="35" valign="top" class="ArticleTeitle"> 因为我们是测试新手,我们也不理会那些复杂的测试原理,先说一说最简单的:测试就是比较预期的结果是否与实际执行的结果一致。如果一致则通过,否则失败。看下面的例子:

<table width="676" border="0"> <tr> <td width="396">//将要被测试的类
public class Car {
public int getWheels() {
return 4;
}
}

//执行测试的类
public class testCar {
public static void main(String[] args) {
testCar myTest = new testCar();
myTest.testGetWheels();
}

public testGetWheels() {
int expectedWheels = 4;
Car myCar = Car();
if (expectedWheels==myCar.getWheels())
System.out.println("test [Car]: getWheels works perfected!");
else
System.out.println("test [Car]: getWheels DOESN'T work!");
}
} </td> <td width="270" valign="top"> </td> </tr> </table>
如果你立即动手写了上面的代码,你会发现两个问题,第一,如果你要执行测试的类testCar,你必须必须手工敲入如下命令:

[Windows] d:>java testCar
[Unix] % java testCar

即便测试如例示的那样简单,你也有可能不愿在每次测试的时候都敲入上面的命令,而希望在某个集成环境中(IDE)点击一下鼠标就能执行测试。后面的章节会介绍到这些问题。第二,如果没有一定的规范,测试类的编写将会成为另一个需要定义的标准。没有人希望查看别人是如何设计测试类的。如果每个人都有不同的设计测试类的方法,光维护被测试的类就够烦了,谁还顾得上维护测试类?另外有一点我不想提,但是这个问题太明显了,测试类的代码多于被测试的类!这是否意味这双倍的工作?不!1)不论被测试类-Car 的 getWheels 方法如何复杂,测试类-testCar 的testGetWheels 方法只会保持一样的代码量。2)提高软件的质量并解决软件熵这一问题并不是没有代价的。testCar就是代价。

我们目前所能做的就是尽量降低所付出的代价:我们编写的测试代码要能被维护人员容易的读取,我们编写测试代码要有一定的规范。最好IDE工具可以支持这些规范。 好了,你所需要的就是JUnit。一个Open Source的项目。用其主页上的话来说就是:“JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。用于Java开发人员编写单元测试之用。”所谓框架就是 Erich Gamma 和 Kent Beck 定下了一些条条框框,你编写的测试代码必须遵循这个条条框框:继承某个类,实现某个接口。其实也就是我们前面所说的规范。好在JUnit目前得到了大多数软件工程师的认可。遵循JUnit我们会得到很多的支持。回归测试就是你不断地对所编写的代码进行测试:编写一些,测试一些,调试一些,然后循环这一过程,你会不断地重复先前的测试,哪怕你正编写其他的类,由于软件熵的存在,你可能在编写第五个类的时候发现,第五个类的某个操作会导致第二个类的测试失败。通过回归测试我们抓住了这条大Bug。


回归测试框架-JUnit
通过前面的介绍,我们对JUnit有了一个大概的轮廓。知道了它是干什么的。现在让我们动手改写上面的测试类testCar使其符合Junit的规范--能在JUnit中运行。

//执行测试的类(JUnit版)
import junit.framework.*;

public class testCar extends TestCase {

protected int expectedWheels;
protected Car myCar;

public testCar(String name) {
super(name);
}

protected void setUp() {
expectedWheels = 4;
myCar = new Car();
}

public static Test suite() {
/*
* the type safe way
*
TestSuite suite= new TestSuite();
suite.addTest(new testCar("Car.getWheels") {
protected void runTest() { testGetWheels(); }
});
return suite;
*/

/*
* the dynamic way
*/
return new TestSuite(testCar.class);
}

public void testGetWheels() {
assertEquals(expectedWheels, myCar.getWheels());
}
}

改版后的testCar已经面目全非。先让我们了解这些改动都是什么含义,再看如何执行这个测试。

1>import语句,引入JUnit的类。(没问题吧)

2>继承 TestCase 。可以暂时将一个TestCase看作是对某个类进行测试的方法的集合。详细介绍请参看JUnit资料

3>setUp()设定了进行初始化的任务。我们以后会看到setUp会有特别的用处。

4>testGetWheeels()对预期的值和myCar.getWheels()返回的值进行比较,并打印比较的结果。assertEquals是junit.framework.Assert中所定义的方法,junit.framework.TestCase继承了junit.framework.Assert。

5>suite()是一个很特殊的静态方法。JUnit的TestRunner会调用suite方法来确定有多少个测试可以执行。上面的例子显示了两种方法:静态的方法是构造一个内部类,并利用构造函数给该测试命名(test name, 如 Car.getWheels ),其覆盖的runTest()方法,指明了该测试需要执行那些方法--testGetWheels()。动态的方法是利用内省(reflection )来实现runTest(),找出需要执行那些测试。此时测试的名字即是测试方法(test method,如testGetWheels)的名字。JUnit会自动找出并调用该类的测试方法。

6>将TestSuite看作是包裹测试的一个容器。如果将测试比作叶子节点的话,TestSuite就是分支节点。实际上TestCase,TestSuite以及TestSuite组成了一个composite Pattern。 JUnit的文档中有一篇专门讲解如何使用Pattern构造Junit框架。有兴趣的朋友可以查看JUnit资料。

如何运行该测试呢?手工的方法是键入如下命令:

[Windows] d:>java junit.textui.TestRunner testCar
[Unix] % java junit.textui.TestRunner testCar

别担心你要敲的字符量,以后在IDE中,只要点几下鼠标就成了。运行结果应该如下所示,表明执行了一个测试,并通过了测试:

.
Time: 0

OK (1 tests)

如果我们将Car.getWheels()中返回的的值修改为3,模拟出错的情形,则会得到如下结果:

.F
Time: 0
There was 1 failure:
1) testGetWheels(testCar)junit.framework.AssertionFailedError: expected:<4> but was:<3>
at testCar.testGetWheels(testCar.java:37)

FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0

注意:Time上的小点表示测试个数,如果测试通过则显示OK。否则在小点的后边标上F,表示该测试失败。注意,在模拟出错的测试中,我们会得到详细的测试报告“expected:<4> but was:<3>”,这足以告诉我们问题发生在何处。下面就是你调试,测试,调试,测试...的过程,直至得到期望的结果。


安装

首先你要获取JUnit的软件包,从JUnit下载最新的软件包(截至写作本文时,JUnit的最新版本是3.7)。将其在适当的目录下解包。这样在安装目录(也就是你所选择的解包的目录)下你找到一个名为junit.jar的文件。将这个jar文件加入你的CLASSPATH系统变量。(IDE的设置会有所不同,参看你所喜爱的IDE的配置指南)JUnit就安装完了。太easy了!

你一旦安装完JUnit,就有可能想试试我们的Car和testCar类,没问题,我已经运行过了,你得到的结果应该和我列出的结果类似。(以防新版JUnit使我的文章过时)

接下来,你可能会先写测试代码,再写工作代码,或者相反,先写工作代码,再写测试代码。我更赞成使用前一种方法:先写测试代码,再写工作代码。因为这样可以使我们编写工作代码时清晰地了解工作类的行为。

要注意编写一定能通过的测试代码(如文中的例子)并没有任何意义,只有测试代码能帮助我们发现bug,测试代码才有其价值。此外测试代码还应该对工作代码进行全面的测试。如给方法调用的参数传入空值、错误值和正确的值,看看方法的行为是否如你所期望的那样。

你现在已经知道了编写测试类的基本步骤:
1>扩展TestCase类;
2>覆盖runTest()方法(可选);
3>写一些testXXXXX()方法; </td> </tr> <tr>


↑返回目录
前一篇: 从键盘获取数据计算大的阶乘
后一篇: 如何打包程序为JAR文件