首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

[转]JUnit课程

2012-12-21 
[转]JUnit教程测试的概念 长期以来,我所接触的软件开发人员很少有人能在开发的过程中进行测试工作。大部分

[转]JUnit教程
测试的概念
长期以来,我所接触的软件开发人员很少有人能在开发的过程中进行测试工作。大部分的项目都是在最终验收的时候编写测试文档。有些项目甚至没有测试文档。现在情况有了改变。我们一直提倡UML、RUP、软件工程、CMM,目的只有一个,提高软件编写的质量。举一个极端的例子:如果你是一个超级程序设计师,一个传奇般的人物。(你可以一边喝咖啡,一边听着音乐,同时编写这操作系统中关于进程调度的模块,而且两天时间内就完成了!)我真得承认,有这样的人。(那个编写UNIX中的vi编辑器的家伙就是这种人。)然而非常遗憾的是这些神仙们并没有留下如何修成正果的README。所以我们这些凡人--在同一时间只能将注意力集中到若干点(据科学统计,我并不太相信,一般的人只能同时考虑最多7个左右的问题,高手可以达到12个左右),而不能既纵览全局又了解细节--只能期望于其他的方式来保证我们所编写的软件质量。
为了说明我们这些凡人是如何的笨。有一个聪明人提出了软件熵(software entropy)的概念:一个程序从设计很好的状态开始,随着新的功能不断地加入,程序逐渐地失去了原有的结构,最终变成了一团乱麻。你可能会争辩,在这个例子中,设计很好的状态实际上并不好,如果好的话,就不会发生你所说的情况。是的,看来你变聪明了,可惜你还应该注意到两个问题:1)我们不能指望在恐龙纪元(大概是十年前)设计的结构到了现在也能适用吧。2)拥有签字权的客户代表可不理会加入一个新功能是否会对软件的结构有什么影响,即便有影响也是程序设计人员需要考虑的问题。如果你拒绝加入这个你认为致命的新功能,那么你很可能就失去了你的住房贷款和面包(对中国工程师来说也许是米饭或面条,要看你是南方人还是北方人)。
另外,需要说明的是我看过的一些讲解测试的书都没有我写的这么有人情味(不好意思...)。我希望看到这片文章的兄弟姐妹能很容易地接受测试的概念,并付诸实施。所以有些地方写的有些夸张,欢迎对测试有深入理解的兄弟姐妹能体察民情,并不吝赐教。
好了,我们现在言归正传。要测试,就要明白测试的目的。我认为测试的目的很简单也极具吸引力:写出高质量的软件并解决软件熵这一问题。想象一下,如果你写的软件和Richard Stallman(GNU、FSF的头儿)写的一样有水准的话,是不是很有成就感?如果你一致保持这种高水准,我保证你的薪水也会有所变动。
测试也分类,白箱测试、黑箱测试、单元测试、集成测试、功能测试...。我们先不管有多少分类,如何分类。先看那些对我们有用的分类,关于其他的测试,有兴趣的人可参阅其他资料。白箱测试是指在知道被测试的软件如何(How)完成功能和完成什么样(What)的功能的条件下所作的测试。一般是由开发人员完成。因为开发人员最了解自己编写的软件。本文也是以白箱测试为主。黑箱测试则是指在知道被测试的软件完成什么样(What)的功能的条件下所作的测试。一般是由测试人员完成。黑箱测试不是我们的重点。本文主要集中在单元测试上,单元测试是一种白箱测试。目的是验证一个或若干个类是否按所设计的那样正常工作。集成测试则是验证所有的类是否能互相配合,协同完成特定的任务,目前我们暂不关心它。下面我所提到的测试,除非特别说明,一般都是指单元测试。
需要强调的是:测试是一个持续的过程。也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式(iterative and incremental)的开发过程。Martin Fowler(有点儿像引用孔夫子的话)甚至认为:“在你不知道如何测试代码之前,就不应该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。”我并不指望所有的开发人员都能有如此高的觉悟,这种层次也不是一蹴而就的。但我们一旦了解测试的目的和好处,自然会坚持在开发过程中引入测试。
因为我们是测试新手,我们也不理会那些复杂的测试原理,先说一说最简单的:测试就是比较预期的结果是否与实际执行的结果一致。如果一致则通过,否则失败。看下面的例子:

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方法检查到的结果。而错误则是意外的问题引起的,如ArrayIndexOutOfBoundsException。
由于TestRunner十分简单,界面也比较直观,故不多介绍。朋友们可自行参考相关资料。

JUnit最佳实践
Martin Fowler(又是这位高人)说过:“当你试图打印输出一些信息或调试一个表达式时,写一些测试代码来替代那些传统的方法。”一开始,你会发现你总是要创建一些新的Fixture,而且测试似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复使用相同的Fixture,而且新的测试通常只涉及添加一个新的测试方法。
你可能会写许多测试代码,但你很快就会发现你设想出的测试只有一小部分是真正有用的。你所需要的测试是那些会失败的测试,即那些你认为不会失败的测试,或你认为应该失败却成功的测试。
我们前面提到过测试是一个不会中断的过程。一旦你有了一个测试,你就要一直确保其正常工作,以检验你所加入的新的工作代码。不要每隔几天或最后才运行测试,每天你都应该运行一下测试代码。这种投资很小,但可以确保你得到可以信赖的工作代码。你的返工率降低了,你会有更多的时间编写工作代码。
不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻,应为通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效率地工作代码。下面是一些具体的编写测试代码的技巧或较好的实践方法:
1. 不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法。
2. 不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。
3. 避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的会滚就可以了。
4. 当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。
5. 将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持junit的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用到了Container的提供的服务,则需要将其部署在Container中才能进行测试。方法与EJB类似。
对于servlet/JSP的测试则比较棘手,有人建议在测试代码中构造HttpRequest和HttpResponse,然后进行比较,这就要求开发人员对HTTP协议以及servlet/JSP的内部实现有比较深的认识。我认为这招不太现实。也有人提出使用HttpUnit。由于我对Cactus和HttpUnit 了解不多,所以无法做出合适的建议。希望各位先知们能不吝赐教。
正是由于JUnit的开放性和简单易行,才会引出这篇介绍文章。但技术总在不断地更新,而且我对测试并没有非常深入的理解;我可以将一个复杂的概念简化成一句非常容易理解的话。但我的本意只是希望能降低开发人员步入测试领域的门槛,而不是要修改或重新定义一些概念。这一点是特别要强调的。最后,如果有些兄弟姐妹能给我指出一些注意事项或我对某些问题的理解有误,我会非常感激的。

热点排行