转载:JUNIT实施(3)
来源:互联网 发布:iptables添加端口模块 编辑:程序博客网 时间:2024/04/29 23:55
//执行测试的类(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>”,这足以告诉我们问题发生在何处。下面就是你调
试,测试,调试,测试...的过程,直至得到期望的结果。
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作为未来版本的一个增强。
Refactoring(这句话我依然没法翻译)
Refactoring本来与测试没有直接的联系,而是与软件熵有
关,但既然我们说测试能解决软件熵问题,我们也就必须说出解
决之道。(仅仅进行测试只能发现软件熵,Refactoring则可解决
软件熵带来的问题。)软件熵引出了一个问题:是否需要重新设
计整个软件的结构?理论上应该如此,但现实不允许我们这么做
。这或者是由于时间的原因,或者是由于费用的原因。重新设计
个软件的结构会给我们带来短期的痛苦。而不停地给软件打补丁甚
至是补丁的补丁则会给我们带来长期的痛苦。(不管怎样,我们总
处于水深火热之中)
Refactoring是一个术语,用于描述一种技术,利用这种技术
我们可以免于重构整个软件所带来的短期痛苦。当你refactor时,
你并不改变程序的功能,而是改变程序内部的结构,使其更易理解
和使用。如:该变一个方法的名字,将一个成员变量从一个类移到
另一个类,将两个类似方法抽象到父类中。所作的每一个步都很小
,然而1-2个小时的Refactoring工作可以使你的程序结构更适合目
前的情况。Refactoring有一些规则:
1> 不要在加入新功能的同时refactor已有的代码。在这两者间
要有一个清晰的界限。如每天早上1-2个小时的Refactoring,其余
时间添加新的功能。
2> 在你开始Refactoring前,和Refactoring后都要保证测试能顺
利通过。否则Refactoring没有任何意义。
3> 进行小的Refactoring,大的就不是Refactoring了。如果你打
算重构整个软件,就没有必要Refactoring了。
只有在添加新功能和调试bug时才又必要Refactoring。不要等到
交付软件的最后关头才Refactoring。那样和打补丁的区别不大。
Refactoring
用在回归测试中也能显示其威力。要明白,我不反对打补丁,但
要记住打补丁是应该最后使用的必杀绝招。(打补丁也需要很高的技
术,详情参看微软网站)
IDE对JUnit的支持
目前支持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
在IDE中如何使用JUnit,是非常具体的事情。不同的IDE有不同的使用
方法。一旦理解了JUnit的本质,使用起来就十分容易了。所以我们不
依赖于具体的IDE,而是集中精力讲述如何利用JUnit编写单元测试代码。
心急的人可参看资料。
JUnit简介
既然我们已经对JUnit有了一个大致的了解,我希望能给大家提供一
个稍微正式一些的编写JUnit测试文档的手册,明白其中的一些关键术语
和概念。但我要声明的是这并不是一本完全的手册,只能认为是一本入
门手册。同其他OpenSource的软件有同样的问题,JUnit的文档并没有商
业软件文档的那种有规则,简洁和完全。由开发人员编写的文档总是说不
太清楚问题,全整的文档需要参考"官方"指南,API手册,邮
件讨论组的邮件,甚至包括源代码中及相关的注释。