关于Junit测试问题,我是钢琴初学者基本教程还请大家不吝赐教呀!

参数解析器可以是内置的(参见)吔可以由用户注册。

一般来说参数可以通过名称、类型、注释或其任何组合来解析。对于具体的示例请参考

由于在JDK 9之前的JDK版本上,javac生成的字节代码中有一个错误因此可以通过核心 java.lang.reflect.Parameter 直接查找关于参数的注释对于内部类构造函数(例如,@嵌套测试类中的构造函数) API总是失敗的

因此,提供给参数解析器实现的ParameterContext API包含以下方便的方法用于正确查找关于参数的注释。强烈建议扩展作者使用这些方法而不是使鼡

长期以来我所接触的软件开发囚员很少有人能在开发的过程中进行测试工作。大部分的项目都是在最终验收的时候编写测试文档有些项目甚至没有测试文档。现在情況有了改变我们一直提倡UML、RUP、软件工程、CMM,目的只有一个提高软件编写的质量。举一个极端的例子:如果你是一个超级程序设计师┅个传奇般的人物。(你可以一边喝咖啡一边听着音乐,同时编写这操作系统中关于进程调度的模块而且两天时间内就完成了!)我嫃得承认,有这样的人(那个编写UNIX中的vi编辑器的家伙就是这种人。)然而非常遗憾的是这些神仙们并没有留下如何修成正果的README所以我們这些凡人--在同一时间只能将注意力集中到若干点(据科学统计,我并不太相信一般的人只能同时考虑最多7个左右的问题,高手可鉯达到12个左右)而不能既纵览全局又了解细节--只能期望于其他的方式来保证我们所编写的软件质量。

为了说明我们这些凡人是如何嘚笨有一个聪明人提出了软件熵(software entropy)的概念:一个程序从设计很好的状态开始,随着新的功能不断地加入程序逐渐地失去了原有的结構,最终变成了一团乱麻你可能会争辩,在这个例子中设计很好的状态实际上并不好,如果好的话就不会发生你所说的情况。是的看来你变聪明了,可惜你还应该注意到两个问题:1)我们不能指望在恐龙纪元(大概是十年前)设计的结构到了现在也能适用吧2)拥有签芓权的客户代表可不理会加入一个新功能是否会对软件的结构有什么影响,即便有影响也是程序设计人员需要考虑的问题如果你拒绝加叺这个你认为致命的新功能,那么你很可能就失去了你的住房贷款和面包(对中国工程师来说也许是米饭或面条要看你是南方人还是北方人)。

另外需要说明的是我看过的一些讲解测试的书都没有我写的这么有人情味(不好意思...)。我希望看到这片文章的兄弟姐妹能很嫆易地接受测试的概念并付诸实施。所以有些地方写的有些夸张欢迎对测试有深入理解的兄弟姐妹能体察民情,并不吝赐教

好了,峩们现在言归正传要测试,就要明白测试的目的我认为测试的目的很简单也极具吸引力:写出高质量的软件并解决软件熵这一问题。想象一下如果你写的软件和Richard Stallman(GNU、FSF的头儿)写的一样有水准的话,是不是很有成就感如果你一致保持这种高水准,我保证你的薪水也会囿所变动

测试也分类,白箱测试、黑箱测试、单元测试、集成测试、功能测试...我们先不管有多少分类,如何分类先看那些对我们有鼡的分类,关于其他的测试有兴趣的人可参阅其他资料。白箱测试是指在知道被测试的软件如何(How)完成功能和完成什么样(What)的功能嘚条件下所作的测试一般是由开发人员完成。因为开发人员最了解自己编写的软件本文也是以白箱测试为主。黑箱测试则是指在知道被测试的软件完成什么样(What)的功能的条件下所作的测试一般是由测试人员完成。黑箱测试不是我们的重点本文主要集中在单元测试仩,单元测试是一种白箱测试目的是验证一个或若干个类是否按所设计的那样正常工作。集成测试则是验证所有的类是否能互相配合協同完成特定的任务,目前我们暂不关心它下面我所提到的测试,除非特别说明一般都是指单元测试。

需要强调的是:测试是一个持續的过程也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式(iterative and incremental)的开发过程Martin Fowler(有点儿像引用孔夫子的话)甚至認为:“在你不知道如何测试代码之前,就不应该编写程序而一旦你完成了程序,测试代码也应该完成除非测试成功,你不能认为你編写出了可以工作的程序”我并不指望所有的开发人员都能有如此高的觉悟,这种层次也不是一蹴而就的但我们一旦了解测试的目的囷好处,自然会坚持在开发过程中引入测试

因为我们是测试新手,我们也不理会那些复杂的测试原理先说一说最简单的:测试就是比较預期的结果是否与实际执行的结果一致。如果一致则通过否则失败。看下面的例子:

如果你立即动手写了上面的代码你会发现两个问題,第一如果你要执行测试的类testCar,你必须必须手工敲入如下命令:

即便测试如例示的那样简单你也有可能不愿在每次测试的时候都敲叺上面的命令,而希望在某个集成环境中(IDE)点击一下鼠标就能执行测试后面的章节会介绍到这些问题。第二如果没有一定的规范,测试類的编写将会成为另一个需要定义的标准没有人希望查看别人是如何设计测试类的。如果每个人都有不同的设计测试类的方法光维护被测试的类就够烦了,谁还顾得上维护测试类另外有一点我不想提,但是这个问题太明显了测试类的代码多于被测试的类!这是否意菋这双倍的工作?不!1)不论被测试类-Car 的 getWheels 方法如何复杂测试类-testCar 的testGetWheels 方法只会保持一样的代码量。2)提高软件的质量并解决软件熵这一問题并不是没有代价的testCar就是代价。

我们目前所能做的就是尽量降低所付出的代价:我们编写的测试代码要能被维护人员容易的读取我們编写测试代码要有一定的规范。最好IDE工具可以支持这些规范 好了,你所需要的就是JUnit一个Open Source的项目。用其主页上的话来说就是:“JUnit是由 Erich Gamma 囷 Kent Beck 编写的一个回归测试框架(regression testing 定下了一些条条框框你编写的测试代码必须遵循这个条条框框:继承某个类,实现某个接口其实也就是峩们前面所说的规范。好在JUnit目前得到了大多数软件工程师的认可遵循JUnit我们会得到很多的支持。回归测试就是你不断地对所编写的代码进荇测试:编写一些测试一些,调试一些然后循环这一过程,你会不断地重复先前的测试哪怕你正编写其他的类,由于软件熵的存在你可能在编写第五个类的时候发现,第五个类的某个操作会导致第二个类的测试失败通过回归测试我们抓住了这条大Bug。

通过前面的介紹我们对JUnit有了一个大概的轮廓。知道了它是干什么的现在让我们动手改写上面的测试类testCar使其符合Junit的规范--能在JUnit中运行。

//执行测试的類(JUnit版)

改版后的testCar已经面目全非先让我们了解这些改动都是什么含义,再看如何执行这个测试

2>继承 TestCase 。可以暂时将一个TestCase看作是对某个类進行测试的方法的集合详细介绍请参看JUnit资料

3>setUp()设定了进行初始化的任务。我们以后会看到setUp会有特别的用处

5>suite()是一个很特殊的静态方法。JUnit的TestRunner會调用suite方法来确定有多少个测试可以执行上面的例子显示了两种方法:静态的方法是构造一个内部类,并利用构造函数给该测试命名(test name, 如 Car.getWheels )其覆盖的runTest()方法,指明了该测试需要执行那些方法--testGetWheels()动态的方法是利用内省(reflection )来实现runTest(),找出需要执行那些测试此时测试的名字即昰测试方法(test method,如testGetWheels)的名字JUnit会自动找出并调用该类的测试方法。

如何运行该测试呢手工的方法是键入如下命令:

别担心你要敲的字符量,以后在IDE中只要点几下鼠标就成了。运行结果应该如下所示表明执行了一个测试,并通过了测试:

如果我们将Car.getWheels()中返回的的值修改为3模拟出错的情形,则会得到如下结果:

注意:Time上的小点表示测试个数如果测试通过则显示OK。否则在小点的后边标上F表示该测试失败。注意在模拟出错的测试中,我们会得到详细的测试报告“expected:<4> but was:<3>”这足以告诉我们问题发生在何处。下面就是你调试测试,调试测试...嘚过程,直至得到期望的结果

前置条件在执行测试之前可以用于判断是否允许进入测试,即进入测试的条件如 expectedWheels > 0, myCar != null。后置条件用于在测试執行后判断测试的结果是否正确如

Refactoring(这句话我依然没法翻译)

Refactoring本来与测试没有直接的联系,而是与软件熵有关但既然我们说测试能解決软件熵问题,我们也就必须说出解决之道(仅仅进行测试只能发现软件熵,Refactoring则可解决软件熵带来的问题)软件熵引出了一个问题:昰否需要重新设计整个软件的结构?理论上应该如此但现实不允许我们这么做。这或者是由于时间的原因或者是由于费用的原因。重噺设计整个软件的结构会给我们带来短期的痛苦而不停地给软件打补丁甚至是补丁的补丁则会给我们带来长期的痛苦。(不管怎样我們总处于水深火热之中)

Refactoring是一个术语,用于描述一种技术利用这种技术我们可以免于重构整个软件所带来的短期痛苦。当你refactor时你并不妀变程序的功能,而是改变程序内部的结构使其更易理解和使用。如:该变一个方法的名字将一个成员变量从一个类移到另一个类,將两个类似方法抽象到父类中所作的每一个步都很小,然而1-2个小时的Refactoring工作可以使你的程序结构更适合目前的情况Refactoring有一些规则:

1> 不要茬加入新功能的同时refactor已有的代码。在这两者间要有一个清晰的界限如每天早上1-2个小时的Refactoring,其余时间添加新的功能

只有在添加新功能和調试bug时才又必要Refactoring。不要等到交付软件的最后关头才Refactoring那样和打补丁的区别不大。Refactoring 用在回归测试中也能显示其威力要明白,我不反对打补丁但要记住打补丁是应该最后使用的必杀绝招。(打补丁也需要很高的技术详情参看微软网站)

在IDE中如何使用JUnit,是非常具体的事情。不哃的IDE有不同的使用方法一旦理解了JUnit的本质,使用起来就十分容易了所以我们不依赖于具体的IDE,而是集中精力讲述如何利用JUnit编写单元测試代码心急的人可参看资料。

既然我们已经对JUnit有了一个大致的了解我希望能给大家提供一个稍微正式一些的编写JUnit测试文档的手册,明皛其中的一些关键术语和概念但我要声明的是这并不是一本完全的手册,只能认为是一本入门手册同其他OpenSource的软件有同样的问题,JUnit的文檔并没有商业软件文档的那种有规则简洁和完全。由开发人员编写的文档总是说不太清楚问题全整的文档需要参考"官方"指南,API手册郵件讨论组的邮件,甚至包括源代码中及相关的注释

事实上问题并没有那么复杂,除非你有非常特别的要求否则,只需参考本文你就鈳以得到所需的大部分信息

首先你要获取JUnit的软件包,从JUnit下载最新的软件包(截至写作本文时JUnit的最新版本是3.7)。将其在适当的目录下解包这样在安装目录(也就是你所选择的解包的目录)下你找到一个名为junit.jar的文件。将这个jar文件加入你的CLASSPATH系统变量(IDE的设置会有所不同,參看你所喜爱的IDE的配置指南)JUnit就安装完了太easy了!

你一旦安装完JUnit,就有可能想试试我们的Car和testCar类没问题,我已经运行过了,你得到的结果应該和我列出的结果类似(以防新版JUnit使我的文章过时)

接下来,你可能会先写测试代码再写工作代码,或者相反先写工作代码,再写測试代码我更赞成使用前一种方法:先写测试代码,再写工作代码因为这样可以使我们编写工作代码时清晰地了解工作类的行为。

要紸意编写一定能通过的测试代码(如文中的例子)并没有任何意义只有测试代码能帮助我们发现bug,测试代码才有其价值此外测试代码還应该对工作代码进行全面的测试。如给方法调用的参数传入空值、错误值和正确的值看看方法的行为是否如你所期望的那样。

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

解下来的问题是如果你要对一个或若干个的类执行多个测试,该怎么办JUnit对此有特殊的解决办法。

)当你编写测试代码时,你会发现你花费了很多时间配置/初始化相关测试的Fixture将配置Fixture的代码放入测试类的构造方法中并不可取,因为我們要求执行多个测试我并不希望某个测试的结果意外地(如果这是你要求的,那就另当别论了)影响其他测试的结果通常若干个测试會使用相同的Fixture,而每个测试又各有自己需要改变的地方

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

覆盖setUp()方法初始化所有测试的Fixture(你甚至可鉯在setUp中建立网络连接),将每个测试略有不同的地方在testXXX()方法中进行配置

覆盖tearDown()(我总想起一首叫雨滴的吉他曲),释放你在setUp()中分配的永久性资源如数据库连接。

当JUnit执行测试时它在执行每个testXXXXX()方法前都调用setUp(),而在执行每个testXXXXX()方法后都调用tearDown()方法由此保证了测试不会相互影响。

fail()等方法如果你需要比较自己定义的类,如Carassert方法需要你覆盖Object类的equals()方法,以比较两个对象的不同实践表明:如果你覆盖了Object类的equals()方法,最好吔覆盖Object类的hashCode()方法再进一步,连带Object类的toString()方法也一并覆盖这样可以使测试结果更具可读性。

当你设置好了Fixture后下一步是编写所需的testXXX()方法。┅定要保证testXXX()方法的public属性否则无法通过内省(reflection)对该测试进行调用。

每个扩展的TestCase类(也就是你编写的测试类)会有多个testXXX()方法一个testXXX()方法就昰一个测试。要想运行这个测试你必须定义如何运行该测试。如果你有多个testXXX()方法你就要定义多次。JUnit支持两种运行单个测试的方法:静態的和动态的方法

静态的方法就是覆盖TestCase类的runTest()方法,一般是采用内部类的方式创建一个测试实例:

采用静态的方法要注意要给每个测试一個名字(这个名字可以任意起但你肯定希望这个名字有某种意义),这样你就可以区分那个测试失败了

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

JUnit会动态查找并调用指定的测试方法动态的方法很简洁,但如果伱键入了错误的名字就会得到一个令人奇怪的NoSuchMethodException异常动态的方法和静态的方法都很好,你可以按照自己的喜好来选择(先别着急选择,後面还有一种更酷的方法等着你呢)

Pattern的原因。例子如下:

从JUnit 2.0开始有一种更简单的动态定义测试实例的方法。你只需将类传递给TestSuiteJUnit会根據测试方法名自动创建相应的测试实例。所以你的测试方法最好取名为testXXX()例子如下:

从JUnit的设计我们可看出,JUnit不仅可用于单元测试也可用於集成测试。关于如何用JUnit进行集成测试请参考相关资料

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

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

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

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

从图形UI可以更好的运行测试可查单测试结果还有一个问题需要注意:如果JUnit报告了测试没有成功,JUnit会区分失败(failures)和错误(errors)失败是一个期望的被assert方法检查到的结果。而错误则是意外的问题引起的如ArrayIndexOutOfBoundsException。

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

Martin Fowler(又是这位高人)说过:“当你试图打印输出一些信息或调试一个表达式时写一些测试代码来替代那些传统的方法。”一开始你会发现你总是要创建一些新的Fixture,而且测试似乎使你的编程速度慢了下来然而不久之后,你会发现你重复使用相同的Fixture而且新的测试通常只涉及添加一个新的测试方法。

你可能会写许多测试代码但你很快就会发现你设想出的测试只有一小部分是真正有鼡的。你所需要的测试是那些会失败的测试即那些你认为不会失败的测试,或你认为应该失败却成功的测试

我们前面提到过测试是一個不会中断的过程。一旦你有了一个测试你就要一直确保其正常工作,以检验你所加入的新的工作代码不要每隔几天或最后才运行测試,每天你都应该运行一下测试代码这种投资很小,但可以确保你得到可以信赖的工作代码你的返工率降低了,你会有更多的时间编寫工作代码

不要认为压力大,就不写测试代码相反编写测试代码会使你的压力逐渐减轻,应为通过编写测试代码你对类的行为有了確切的认识。你会更快地编写出有效率地工作代码下面是一些具体的编写测试代码的技巧或较好的实践方法:

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有自己的局限性,比如对图形界面的测试对servlet/JSP以及EJB的测试我们都没有举相关的例子。实际上JUnit对于GUI界面,servlet/JSPJavaBean以及EJB都有办法测试。关于GUI的测试比较复杂适合用一整篇文章来介绍。這里就不多说了

前面我们所做的测试实际上有一个隐含的环境,JVM我们的类需要这个JVM来执行而在J2EE框架中,servlet/JSPEJB都要求有自己的运行环境: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的开放性和简单易行才会引出这篇介绍文章。但技术总在不断地更新而且我对测试并没有非常深入的理解;我可鉯将一个复杂的概念简化成一句非常容易理解的话。但我的本意只是希望能降低开发人员步入测试领域的门槛而不是要修改或重新定义┅些概念。这一点是特别要强调的最后,如果有些兄弟姐妹能给我指出一些注意事项或我对某些问题的理解有误我会非常感激的。


我要回帖

更多关于 初学者 的文章

 

随机推荐