测试驱动的开发概述
作者: 未知 来源: 网络转载
一、简介(Introduction)
你可能已经听说了这个新名词:测试驱动的开发(test-driven development),它在广大程序员、各种杂志和网络中程序员常去的地方都非常流行。它究竟是什么呢?测试驱动的开发是一种方法理论,它强调把测试作为开发过程的一个主要部分。要是关心程序质量的话,在部署之前前就应该测试。几乎所有人都使用某种方法进行测试,所以你可能会问:测试不已经是开发过程的一个部分了吗?测试驱动的开发要让测试成为开发过程的主要部分。
二、手工测试(Manual Testing)
早期的软件开发时,许多人的测试方法就是运行程序,通过手工操作来指定输入然后观察输出。现在还有很多软件的开发继续着这种方式。它有一个关键的优点:非常容易学习和理解。如果你有键盘和鼠标,并且会操作图形界面,你就可以测试任何桌面应用。如果你有web浏览器,你还可以测试web应用。这根本谈不上什么技巧,你可以认为所有开发人员都能做到。手工操作时你看到的应用和最终用户看到的完全相同,这也是一个不错的主意,但并不是真正的优点,因为其它的测试方法也能做到。
手工测试的缺点就实在太多了:
手工测试需要反复进行。每一次修改程序,不管是增加新特征,改变已有行为,还是修改bug,你都必须重新测试被影响的部分,才能保证你增删改的代码不会造成破坏。
手工测试可能带来错误。人类不适合作重复性的工作,特别是这个工作还很烦人的话。人类经常忽略细节,这会导致代码被破坏。更可能造成的问题是:修改这个地方,可能会影响到另一些代码的行为,尽管它们的关系不是很明显。因此,即使你作了非常认真负责的测试,那也只限于你认为修改可能影响到的部分,你还是有可能会在其它地方漏掉一些bug或被破坏的功能,不是因为你不够仔细,而是因为软件开发是综合性的工作,软件的各个部分常常有关联,在现在的软件项目中,任何人都不可能详细了解某段代码所有的依赖和被依赖关系。
手工测试无法对不可视组件进行单独测试。如果你只测你能看到的,那你就没测你看不到的。这看上去是非常简单的一个命题,但它的意义重大。软件是复杂的,为了定位和解决错误或被破坏的代码,大量形态各异的bug需要通过“剥茧抽丝”式的测试去分析每一个组件的行为。对于服务器端软件尤为如此。服务器端程序中,几乎所有的重要代码都在逻辑层,如基于web的程序。表示层测试只能间接的测试业务逻辑,可能忽略某些细节。你当然希望逻辑层和表示层都能很好的工作,但逻辑层正常工作是整个应用能正常运行的基础。
如果你的测试是手工进行的,别人就没法判断你的程序功能是否正确。其他人(例如其他开发人员或者甚至是最终用户)只能接受你的承诺:你作了测试,各方面都满足需求。除了你的书面或口头声明保证你对程序作过测试它满足所有可视的检查外,其他人没办法验证功能正确,除非他们自己辛苦的作一遍测试,但这个工作可能并不适合他们,因为他们不熟悉程序的边界条件和逻辑。
三、自动测试(Automated Testing)
自动测试解决了手工测试的不足。因此,要回答“除了软件开发中一直在做的那些事情外,测试驱动的开发到底意味着什么?”这个问题,测试驱动的开发在整个开发过程中引入自动测试,并不断改进这些测试以适应程序代码的扩展。注意这中间的两个要点,第一,测试是自动的表示它不但可以重复进行,还要很方便移植,可重复是指你可以一遍又一遍的对同样的代码进行测试,并且每次都得到同样的结果;方便移植是指别人可以使用你的测试,自己来验证你的程序是否能通过这些测试。第二点要求测试的改进包含到程序本身代码的改进中,测试落后于代码可不是件好事,因为这样测试就不能真正的验证程序功能正确与否,因此测试代码需要与应用本身的代码保持同步前进。
软件的自动测试有两个主要方法。第一个是通过可重现的recorded macros。Mercury Interactive (http://www.mercuryinteractive.com/products/winrunner/)的WinRunner等就是用的这种方法。尽管很容易建立,但宏是很不稳定的,需要经常修改,因为它们通常依赖于按钮和组件的物理位置,而不是在parsed document中的结构位置。对开发人员来说,测试框架不管是产生不正确的错误信息,还是需要大量的工作才能保持和代码同步,都是非常痛苦的事情,
第二个进行web应用自动测试的主要方法就是通过编程API。这样你就有了测试框架,软件库可以检查条件是否满足,报告错误的数量和类型,你可以在测试代码中调用这个框架。你的测试代码就是继承自测试框架的一套标准Java类,它们从应用代码中初始化对象,调用方法来验证给定输入会得到预期的结果。采用编程API方案的包括JUnit、HttpUnit、各种单元测试和黑盒web测试的工具等等。这种方案非常灵活,大多数情况下它大大减少了测试代码的维护时间,并且使应用中的复杂功能测试成为可能,尤其是服务器端应用。这种测试可交替的调用programmatic testing,API-driven testing,或者programmatic API-driven testing。
相对于recorded macros模式来说,基于API的自动测试方法的第一个弱点是它的需要更长创建时间。当你的问题是鼠标移动和点击时很难减少设置时间。第二个问题是绝大多数客户不会写测试程序的。客户了解的是业务过程,而不是技术,客户可能觉得移动鼠标和点击鼠标容易得多,这一点非常重要,如果你想让客户在开发过程中就参与进来的话,客户参与是极限编程的鼓吹者推荐的方法。
虽然如此,基于API的方法在许多方面存在着优越性,可以在大多数应用中使用,因为应用程序随时间改变的程序非常大,所以花在测试程序维护上的时间比测试程序的创建时间占的比例更大。而且recorded macro方法有一个致命限制:只有在应用代码写完之后你才能建立测试。如果你们的开发习惯是不在程序完成前测试,那非常好。否则,如果你坚持测试先行的习惯,正如作者推荐的,那非常不幸recorded macros不适合你,因为在记录宏进行重放之前,你必须有一个能运行的应用程序。
好的方案可能既有recorded macros测试,又有基于API的程序化测试。通过鼠标拖点式测试你可以让最终用户参与到测试中来,保证你的程序满足业务需求。通过程序化测试则可以在技术角度确保程序组件按预计的情况工作。
使用程序化测试,你有两种选择:功能测试和单元测试。功能测试也叫做黑盒测试,是指在不知道(或者忽略)内部实现的情况下,在一个较高的层次上进行测试。功能测试用于验证程序是否完成业务需求,它模拟采用与最终用户一致的方式与程序进行交互。最终用户可能是使用基于web应用的业务人员,也可能是通过你所提供的API来使用你的类库的开发人员。如果你一定要纠缠概念,功能测试和黑盒测试还是有一些区别的,因为技术上功能测试可以在容器内进行(如果要测得是web程序的话),但实际上绝大多数功能测试是在黑盒中做,除了公布的接口外你一无所知。相反,单元测试包括底层代码的验证,为了对确保内部组件没有问题,必须了解程序的内部结构。你还需要知道那些类和方法的实现。如果要测的程序是给开发人员使用的软件库,你的单元测试包括所有重要的类和方法,甚至在发布给用户的API文档中没有列出的内容。功能测试与程序交互的方式是通过点击按钮和信息入口,进入程序可见的forms。单元测试与程序的交互是通过Java方法调用来访问。
对前面提到的手工测试的四个缺陷,自动测试都给出了很好的解决: