TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
因为我们是测试新手,我们也不理会那些复杂的测试原理,先说一说最简单的:测试就是比较预期的结果是否与实际执行的结果一致。如果一致则通过,否则失败。看下面的例子:
//将要被测试的类
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!");
}
}
如果你立即动手写了上面的代码,你会发现两个问题,第一,如果你要执行测试的类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()方法; |
|