JUNIT实施(4)

来源:互联网 发布:iptables添加端口模块 编辑:程序博客网 时间:2024/04/29 20:42
安装

首先你要获取JUnit的软件包,从JUnit下载最新的软件包(截至写作本文时,

JUnit的最新版本是3.7)。将其在适当的目录下解包。这样在安装目录(也就是你所选

择的解包的目录)下你找到一个名为junit.jar的文件。将这个jar文件加入你的CLASSP

ATH系统变量。(IDE的设置会有所不同,参看你所喜爱的IDE的配置指南)JUnit就安装

完了。太easy了!

你一旦安装完JUnit,就有可能想试试我们的Car和testCar类,没问题,我已经

运行过了,你得到的结果应该和我列出的结果类似。(以防新版JUnit使我的文章过时)



接下来,你可能会先写测试代码,再写工作代码,或者相反,先写工作代码,

再写测试代码。我更赞成使用前一种方法:先写测试代码,再写工作代码。因为这样可

以使我们编写工作代码时清晰地了解工作类的行为。

要注意编写一定能通过的测试代码(如文中的例子)并没有任何意义,只有测

试代码能帮助我们发现bug,测试代码才有其价值。此外测试代码还应该对工作代码进行

全面的测试。如给方法调用的参数传入空值、错误值和正确的值,看看方法的行为是否

如你所期望的那样。

你现在已经知道了编写测试类的基本步骤:

1> 扩展TestCase类;

2> 覆盖runTest()方法(可选);

3> 写一些testXXXXX()方法;

Fixture

解下来的问题是,如果你要对一个或若干个的类执行多个测试,该怎么办?JU

nit对此有特殊的解决办法。

如果需要在一个或若干个的类执行多个测试,这些类就成为了测试的context。

在JUnit中被称为Fixture(如testCar类中的 myCar

和 expectedWheels )。当你编写测试代码时,你会发现你花费了很多时间配

置/初始化相关测试的Fixture。将配置Fixture的代码放入测试类的构造方法中并不可取

,因为我们要求执行多个测试,我并不希望某个测试的结果意外地(如果这是你要求的

,那就另当别论了)影响其他测试的结果。通常若干个测试会使用相同的Fixture,而每

个测试又各有自己需要改变的地方。

为此,JUnit提供了两个方法,定义在TestCase类中。

protected void setUp() throws java.lang.Exception

protected void tearDown() throws java.lang.Exception

覆盖setUp()方法,初始化所有测试的Fixture(你甚至可以在setUp中建立网络

连接),将每个测试略有不同的地方在testXXX()方法中进行配置。

覆盖tearDown()(我总想起一首叫雨滴的吉他曲),释放你在setUp()中分配的

永久性资源,如数据库连接。

当JUnit执行测试时,它在执行每个testXXXXX()方法前都调用setUp(),而在执

行每个testXXXXX()方法后都调用tearDown()方法,由此保证了测试不会相互影响。

TestCase

需要提醒一下,在junit.framework.Assert类中定义了相当多的assert方法,

主要有assert(), assert(),

assertEquals(), assertNull(), assertSame(), assertTrue(), fail()等方

法。如果你需要比较自己定义的类,如Car。assert方法需要你覆盖Object类的equals(

)方法,以比较两个对象的不同。实践表明:如果你覆盖了Object类的equals()方法,最

好也覆盖Object类的hashCode()方法。再进一步,连带Object类的toString()方法也一

并覆盖。这样可以使测试结果更具可读性。

当你设置好了Fixture后,下一步是编写所需的testXXX()方法。一定要保证te

stXXX()方法的public属性,否则无法通过内省(reflection)对该测试进行调用。

每个扩展的TestCase类(也就是你编写的测试类)会有多个testXXX()方法。一

个testXXX()方法就是一个测试。要想运行这个测试,你必须定义如何运行该测试。如果

你有多个testXXX()方法,你就要定义多次。JUnit支持两种运行单个测试的方法:静态

的和动态的方法。

静态的方法就是覆盖TestCase类的runTest()方法,一般是采用内部类的方式创

建一个测试实例:

TestCase test01 = new testCar("test getWheels") {

public void runTest() {

testGetWheels();

}

}

采用静态的方法要注意要给每个测试一个名字(这个名字可以任意起,但你肯

定希望这个名字有某种意义),这样你就可以区分那个测试失败了。

动态的方法是用内省来实现runTest()以创建一个测试实例。这要求测试的名字

就是需要调用的测试方法的名字:

TestCase test01 = new testCar("testGetWheels");

JUnit会动态查找并调用指定的测试方法。动态的方法很简洁,但如果你键入了

错误的名字就会得到一个令人奇怪的NoSuchMethodException异常。动态的方法和静态的

方法都很好,你可以按照自己的喜好来选择。(先别着急选择,后面还有一种更酷的方

法等着你呢。)

TestSuite

一旦你创建了一些测试实例,下一步就是要让他们能一起运行。我们必须定义

一个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);

}

从JUnit的设计我们可看出,JUnit不仅可用于单元测试,也可用于集成测试。

关于如何用JUnit进行集成测试请参考相关资料。

为了兼容性的考虑,下面列出使用静态方法的例子:

public static Test suite() {

TestSuite suite= new TestSuite();

suite.addTest(

new testCar("getWheels") {

protected void runTest() { testGetWheels(); }

}

);

suite.addTest(

new testCar("getSeats") {

protected void runTest() { testGetSeats(); }

}

);

return suite;

}

TestRunner

有了TestSuite我们就可以运行这些测试了,JUnit提供了三种界面来运行测试



[Text UI] junit.textui.TestRunner

[AWT UI] junit.awtui.TestRunner

[Swing UI] junit.swingui.TestRunner

我们前面已经看过文本界面了,下面让我们来看一看图形界面:

界面很简单,键入类名-testCar。或在启动UI的时候键入类名:

[Windows] d:>java junit.swingui.TestRunner testCar

[Unix] % java junit.swingui.TestRunner testCar

从图形UI可以更好的运行测试可查单测试结果。还有一个问题需要注意:如果

JUnit报告了测试没有成功,JUnit会区分失败(failures)和错误(errors)。失败是

一个期望的被assert方法检查到的结果。而错误则是意外的问题引起的,如ArrayIndex

OutOfBoundsException。

由于TestRunner十分简单,界面也比较直观,故不多介绍。朋友们可自行参考

相关资料。

JUnit最佳实践

Martin Fowler(又是这位高人)说过:“当你试图打印输出一些信息或调试一个表达

式时,写一些测试代码来替代那些传统的方法。”一开始,你会发现你总是要创建一些

新的Fixture,而且测试似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复

使用相同的Fixture,而且新的测试通常只涉及添加一个新的测试方法。

你可能会写许多测试代码,但你很快就会发现你设想出的测试只有一小部分是真正有

用的。你所需要的测试是那些会失败的测试,即那些你认为不会失败的测试,或你认为

应该失败却成功的测试。

我们前面提到过测试是一个不会中断的过程。一旦你有了一个测试,你就要一直确保

其正常工作,以检验你所加入的新的工作代码。不要每隔几天或最后才运行测试,每天

你都应该运行一下测试代码。这种投资很小,但可以确保你得到可以信赖的工作代码。

你的返工率降低了,你会有更多的时间编写工作代码。

不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻,应为

通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效率地工作代

码。下面是一些具体的编写测试代码的技巧或较好的实践方法:

1. 不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法。

2. 不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同

的平台会按不同的顺序从Vector中取出测试方法。

3. 避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据

,就不要提交交易数据。简单的会滚就可以了。

4. 当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。

5. 将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持juni

t的task.)

6. 测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试

类名。

7. 确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程

中很难重现测试。

8. 如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅用母语

的Locale进行测试。

9. 尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为

简洁。

10.测试要尽可能地小,执行速度快。

事实上,JUnit还可用于集成测试,但我并没涉及到,原因有两个:一是因为没有单元

测试,集成测试无从谈起。我们接受测试地概念已经很不容易了,如果再引入集成测试

就会更困难。二是我比较懒,希望将集成测试的任务交给测试人员去做。在JUnit的网站

上有一些相关的文章,有空大家可以翻一翻。

JUnit与J2EE

如果大家仔细考虑一下的话,就会发现,JUnit有自己的局限性,比如对图形界面的测

试,对servlet/JSP以及EJB的测试我们都没有举相关的例子。实际上,JUnit对于GUI界

面,servlet/JSP,JavaBean以及EJB都有办法测试。关于GUI的测试比较复杂,适合用一

整篇文章来介绍。这里就不多说了。

前面我们所做的测试实际上有一个隐含的环境,JVM我们的类需要这个JVM来执行。而

在J2EE框架中,servlet/JSP,EJB都要求有自己的运行环境:Web

Container和EJB Container。所以,要想对servlet/JSP,EJB进行测试就需要将其部

署在相应的Container中才能进行测试。由于EJB不涉及UI的问题(除非EJB操作XML数据

,此时的测试代码比较难写,有可能需要你比较两棵DOM树是否含有相同的内容)只要部

署上去之后就可以运行测试代码了。此时setUp()方法显得特别有用,你可以在setUp()

方法中利用JNDI查找特定的EJB。而在testXXX()方法中调用并测试这些EJB的方法。

这里所指的JavaBean同样没有UI的问题,比如,我们用JavaBean来访问数据库,或用

JavaBean来包裹EJB。如果这类JavaBean没有用到Container的提供的服务,则可直接进

行测试,同我们前面所说的一般的类的测试方法一样。如果这类JavaBean用到了Contai

ner的提供的服务,则需要将其部署在Container中才能进行测试。方法与EJB类似。 对

于servlet/JSP的测试则比较棘手,有人建议在测试代码中构造HttpRequest和HttpResp

onse,然后进行比较,这就要求开发人员对HTTP协议以及servlet/JSP的内部实现有比较

深的认识。我认为这招不太现实。也有人提出使用HttpUnit。由于我对Cactus和HttpUn

it 了解不多,所以无法做出合适的建议。希望各位先知们能不吝赐教。

正是由于JUnit的开放性和简单易行,才会引出这篇介绍文章。但技术总在不断地更新

,而且我对测试并没有非常深入的理解;我可以将一个复杂的概念简化成一句非常容易

理解的话。但我的本意只是希望能降低开发人员步入测试领域的门槛,而不是要修改或

重新定义一些概念。这一点是特别要强调的。最后,如果有些兄弟姐妹能给我指出一些

注意事项或我对某些问题的理解有误,我会非常感激的。
 
原创粉丝点击