测试驱动对Java开发的重要性
by Peter Varhol 来自发赛特网站
测试驱动开发(test-driven development)为确保程序质量提供了一种方法。
我以前曾经谈到过关于程序的性能以及性能测试的问题,即以提供工具和技术为目标,通过使用JavaServer Pages (JSP)使用户提高分布式程序的开发。通过性能测试能够确保程序满足用户关于实现有效的互用性的需求。同样地,加载测试还能够确保在用户大量增加时持续满足所有用户的需求。
然而测试是一个比普通的性能测试和加载测试内容更广泛的行为,它是所有软件开发过程中的必须过程。同时它能帮助你养成一个良好的测试习惯,即使是对一种新技术的研究也是如此。通过测试能够看到你的代码会在哪里出错,然后可以根据提示信息对出错的地方进行更正,并以此提高你这方面的技术。
为了使程序开发更为严谨,你就得接受更大的挑战。测试不仅是为了找到程序的错误,更确切地说,你应该把出错的定义加以扩大,使它不但包括程序的错误,还包括在什么地方不能满足用户的需求,以及程序是否按照你希望的那样运行。如果你写代码是为了自己的程序,那么你也许能够容忍程序无法实现你所有需求,或是看起来有点怪。而如果是作为商业目的,则能否满足用户需求以及提供完美的用户体验将成为决定全部工作成功与否的关键。
为了满足用户的需求,必须对程序进行测试,这不仅是为了保证质量,而且是为了保证程序和既定的目标相符合。话虽如此,但在实践中却很难做到,因为用户常常没有一个清楚的需求,而且开发人员也很少能专心地研究这些这些需求。在一个高价程序开发环境下,开发小组要求具备有追溯能力(traceability)的软件以实现按需求进行编码以及全程跟踪直至完成。
很难实现既满足质量要求又符合用户需求的测试,这常常是程序做得很烂的借口,同时也是企业对程序开发的某种商业托词。这些情况的发生往往不是因为软件开发的很失败,而是因为测试程序出了问题。
这就是为什么我要强调测试驱动开发的原因了。我曾花时间研究过整套测试的方法,结果我选择了测试驱动开发。测试驱动开发是由Peter Coad定义的一个开发式样:
编写和保持一套详尽的单元测试。
要先建立相关的单元测试和验收(acceptance)测试,然后根据测试编写代码。
由测试来决定如何编写代码。
这种观点强调在写程序之前先写测试,这会使开发人员更清楚应该在程序中加入何种性能,以及如何使这些性能良好地运行。编写适合的测试会使开发人员清除各种矛盾以满足用户需求。
对大多数程序代码的编写来说,这种观点是正确的,由于许多需求并不明确或并不完整,开发人员不得不花时间把它弄清楚或是根据以往的实践经验来选择一个较好的办法。如果所有不明确的问题都能在写测试的时候首先被解决,那么就会使之后复杂的编写代码工作变得很轻松。
先写测试还会带来一些心理上的影响。如果开发人员在写代码之前先写测试,他们就会更多地考虑到用户的需求,而如果让他们先写代码,那么他们自然会倾向于如何避免被测出程序上的错误。
顺便提一下,如今测试驱动开发已经成为了软件开发过程中不可或缺的一部分,而它是在15年前由the Department of Defense所定义的。由于该标准符合当时的情况,所以编写代码通常是整个过程的最后一步,你很清楚该写哪一类型的代码以及如何对它进行修改。尽管这样会多花些时间,但结果通常能满足最后的需求。
极端编程(Extreme Programming)
采用测试驱动开发是极端编程(Extreme Programming,简称XP)的一个特点。把它叫做极端编程 (www.extremeprogramming.org)可能有些用词不当,因为它代表了传统标准下定义的非传统编程式样,但它却很符合通常的软件工程实践(software engineering practice)。一般来说,XP提倡将一个软件项目划分为一系列小的迭代期(increment),每个迭代期(周期为几天)完成项目的一个小部分。迭代期确定的最终原则是让用户尽早拿到一个可用的早期版本,以便用户提供反馈信息,并对项目进行互动性地更改或添加新的代码。
XP还鼓励配对编程(paired programming),就是让能力相当的开发人员坐在一起编写代码,这通常能够实现高度交互式编程。这样做的目的是让一对开发人员共同研究一个项目以实现其能力互补。
测试也是XP开发的重要一项。开发人员能从代码的逐一测试中得到有用的反馈信息。单元测试是XP开发方法中的基石,开发人员必须确保在交货前,所有的代码均进行并通过了单元测试,以及所有功能均满足用户的需求。
然而XP并不适用于所有的项目或所有开发小组,比如无法将整个项目划分为若干各迭代期或者开发人员的技能无助于合作编码等。在某些情况下,开发条件无法保证对变化需求做出及时的响应。
此时你仍然可以采取测试驱动的办法,而无须贸然使用XP。测试驱动开发使测试工作和开发工作的重要性处于同一级别,减少事后发现错误的情况。
测试框架是测试驱动开发的关键,尤其是在需要用大量测试来实现需求的情况下。如果你完全靠手工来执行测试,那么它会变成一个花费大量时间而且单调无味的工作。测试框架能够使你的测试和开发环境相融合,指导测试程序,收集并显示相关信息。
对于测试框架的选择,我建议使用JUnit,它是一个来自于SourceForge Java foundry或者 www.junit.org 的open-source项目,它是由Erich Gamma 和 Kent Beck编写的一个基于Java的测试框架,使单元测试融合到一个更大的测试框架里面以便更容易地增加、管理、执行测试,以及分析测试结果。
比如,你可以通过在TestCase的子类中增加一个名为myTest()的测试方法来对它进行测试,JUnit测试方法是一个不带任何参数的普通的方法。为了执行连续的多重测试,JUnit提供一个名为TestSuite的对象,它可以同时运行任意数量的测试。TestSuite对象中还可以包括多个小的TestSuites,以便每个开发人员能够单独处理其中一个TestSuite,然后再很方便地将它们结合到这个大的TestSuite中。
为了能够自动执行测试,JUnit提供了一种工具,它可以用来定义suite运行并显示其结果。为了使你的suite对于TestRunner是可存取的,你必须定义一个返回测试suite的静止方法。JUnit提供两种TestRunner类型。第一个是基于文本的TestRunner,该版本能够以最快速度存取,而且适用于无需立即知道单个的测试是否成功的时候。另一个是图解TestRunner,它在进入测试时显示一个对话框,并在执行期间提供一个图解过程指示。
TestRunner可以用在加载和卸载的配置中。在加载配置中,TestRunner在每一次运作中重新加载测试类。其结果是在改变代码之后你无需重新启动TestRunner,它能够很好地实现stop-change-start这一连续动作。而在卸载配置中你必须在每次运行之后重新启动TestRunner。
测试工作会逐渐减少在找出程序错误方面的功能,而越来越多地被用在衡量软件的质量上。它能够提供一种获得数据的方法,该数据可以用在对开发过程的反馈中。但要实现这一点,你必须有一个良好的测试体系。
关于作者:
Peter Varhol是Compuware Corporation的技术传播者。你可以通过peterv@mv.mv.com联系他。