需要强调的是:测试是一个持续的过程。也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式(iterative and incremental)的开发过程。Martin Fowler(有点儿像引用孔夫子的话)甚至认为:“在你不知道如何测试代码之前,就不应该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。”我并不指望所有的开发人员都能有如此高的觉悟,这种层次也不是一蹴而就的。但我们一旦了解测试的目的和好处,自然会坚持在开发过程中引入测试。
我们目前所能做的就是尽量降低所付出的代价:我们编写的测试代码要能被维护人员容易的读取,我们编写测试代码要有一定的规范。最好IDE工具可以支持这些规范。 好了,你所需要的就是JUnit。一个Open Source的项目。用其主页上的话来说就是:“JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。用于Java开发人员编写单元测试之用。”所谓框架就是 Erich Gamma 和 Kent Beck 定下了一些条条框框,你编写的测试代码必须遵循这个条条框框:继承某个类,实现某个接口。其实也就是我们前面所说的规范。好在JUnit目前得到了大多数软件工程师的认可。遵循JUnit我们会得到很多的支持。回归测试就是你不断地对所编写的代码进行测试:编写一些,测试一些,调试一些,然后循环这一过程,你会不断地重复先前的测试,哪怕你正编写其他的类,由于软件熵的存在,你可能在编写第五个类的时候发现,第五个类的某个操作会导致第二个类的测试失败。通过回归测试我们抓住了这条大Bug。
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());
}
}
如果我们将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>”,这足以告诉我们问题发生在何处。下面就是你调试,测试,调试,测试...的过程,直至得到期望的结果。
Design by Contract(这句话我没法翻译)
Design by Contract本是Bertrand Meyer(Eiffel语言的创始人)开发的一种设计技术。我发现在JUnit中使用Design by Contract会带来意想不到的效果。Design by Contract的核心是断言(assersion)。断言是一个布尔语句,该语句不能为假,如果为假,则表明出现了一个bug。Design by Contract使用三种断言:前置条件(pre-conditions)、后置条件(post-conditions)和不变式(invariants)这里不打算详细讨论Design by Contract的细节,而是希望其在测试中能发挥其作用。
前置条件在执行测试之前可以用于判断是否允许进入测试,即进入测试的条件。如 expectedWheels > 0, myCar != null。后置条件用于在测试执行后判断测试的结果是否正确。如 expectedWheels==myCar.getWheels()。而不变式在判断交易(Transaction)的一致性(consistency)方面尤为有用。我希望JUnit可以将Design by Contract作为未来版本的一个增强。
目前支持JUnit的Java IDE 包括 IDE 方式 个人评价(1-5,满分5)
Forte for Java 3.0 Enterprise Edition plug-in 3
JBuilder 6 Enterprise Edition integrated with IDE 4
Visual Age for Java support N/A
一旦你创建了一些测试实例,下一步就是要让他们能一起运行。我们必须定义一个TestSuite。在JUnit中,这就要求你在TestCase类中定义一个静态的suite()方法。suite()方法就像main()方法一样,JUnit用它来执行测试。在suite()方法中,你将测试实例加到一个TestSuite对象中,并返回这个TestSuite对象。一个TestSuite对象可以运行一组测试。TestSuite和TestCase都实现了Test接口(interface),而Test接口定义了运行测试所需的方法。这就允许你用TestCase和TestSuite的组合创建一个TestSuite。这就是为什么我们前面说TestCase,TestSuite以及TestSuite组成了一个composite Pattern的原因。例子如下:
public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTest(new testCar("testGetWheels"));
suite.addTest(new testCar("testGetSeats"));
return suite;
}
从JUnit 2.0开始,有一种更简单的动态定义测试实例的方法。你只需将类传递给TestSuite,JUnit会根据测试方法名自动创建相应的测试实例。所以你的测试方法最好取名为testXXX()。例子如下:
public static Test suite() {
return new TestSuite(testCar.class);
}
Martin Fowler(又是这位高人)说过:“当你试图打印输出一些信息或调试一个表达式时,写一些测试代码来替代那些传统的方法。”一开始,你会发现你总是要创建一些新的Fixture,而且测试似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复使用相同的Fixture,而且新的测试通常只涉及添加一个新的测试方法。