除了构造一个容器来进行测试之外,另一种方式则从根本上改变了控制器的测试方法,那就是不将控制器实现在Servlet技术之上。这样做的好处是测试无需依赖于容器。典型的如springframework(http://www.springframework.org)的Web层就是这样设计的。
无论采用何种方法,都说明软件业在对界面的自动化测试已经开始充分的关注,并提出了不少的解决方法了。这样做的目的无非是两点:一是通过自动化测试提高软件质量,二是通过对测试的关注来推动设计的优化。
最后,从开发文化上来看。界面自动化测试的要求意味着开发人员需要先和用户进行充分的沟通,绘制出满足需要的页面,这其实是原型方法的应用,对开发过程是有利的。此外,开发人员需要慎重的思考页面的设计,保证页面设计的抗变性和可扩展性,否则,你会发现测试代码变得非常的不稳定,从而导致一些不必要的麻烦。这种文化将会推动设计的发展。
所以我们看到,在一个成本较高的自动化测试领域,通过合理的设计和引入工具,可以降低自动化测试的成本。而且,在上述的讨论中,我们也发现,之所以自动化测试的成本高昂,往往是由于设计不当而造成的。在界面混杂大量的逻辑,导致变化不断发生,不但代码需要修改,测试代码同样需要修改,设计的随便才是高成本的真正罪魁祸首。也正是因为这个原因,测试才能够驱动设计的优化。
测试的分类
单元测试
单元测试是典型的对代码逻辑的黑盒测试。在测试驱动方法中,不太强调白盒测试(绝大多数的白盒测试都是通过评审进行的)。这样做的好处是关注接口胜于关注实现,这是一种分析复杂软件的有效办法。这一点我们在后续的文章中还会讨论。
单元测试是开发人员的职责。一般来说,测试的编码最好由不同人来负责,避免出现盲点,以提高测试的有效性。但是单元测试的粒度很小,如果进行分工,沟通的成本会相当高。此外,采用测试优先的实践,对测试进行适当的培训,也能够有效的降低单个人的盲点范围。
单元测试可以加入到小组日构建中,也可以不加入。如果不加入,那么需要有一种机制来管理单元测试活动。
集成测试
集成测试的粒度和测试的范围要比单元测试大。我们拿数据库测试来做例子。现在需要对一个业务对象进行测试,它需要用到持久化机制。在单元测试中,我们将不涉及数据库而单独对业务对象进行测试(使用MO技术,下文中讨论);但是在集成测试中,我们需要将数据库的数据一致性也纳入进来,所以测试包括数据库数据的建立,测试业务方法,使数据库恢复原状。
集成测试应该是日构建的重要组成部分,即日构建标准中的测试标准。最好将集成测试交给QA部门负责。QA部门的精力可以放在使用或编写一些工具(Cactus就是典型的集成测试工具),建立标准的测试数据,安排测试计划等活动上。
接受测试
有时候很难区分集成测试和接受测试。接受测试的关注点是用户。用户通过将数据输入系统来观察系统的输出。所以,了解用户的需要并将用户的需要转换为接受测试是接受测试中最关键的工作。接受测试处于测试过程的最后环节,是判断软件是否满足用户需求的试金石。毫不例外。接受测试也应该是自动化的。例如,HttpUnit就是一个自动化接受测试的工具。另外,很多的专业测试工具提供的自动化的脚本测试工具也属于这个范畴。
测试推进设计
在界面测试的讨论中,我们已经认识到测试是可以推动设计的。在这里我们打算结合具体的技术来讨论测试和设计之间的关系。
针对接口设计
测试驱动方法采用的是黑盒测试,为了保证测试的稳定性,被测代码接口的稳定性是非常重要的。否则,变化的成本就会急剧的上升。所以,自动化测试将会要求你的设计依赖于接口,而不是具体的类。这种设计目前被公认是较好的设计思路。
MO技术
这里不会介绍MO(Mock Object,伪对象)技术,DW专区中已经有这方面的讨论文章了。这里的重点是讨论如何正确的使用MO技术。用过MO的人都有这样的疑惑,MO技术太麻烦了,编写大量的伪对象仅仅是为了配合被测试对象的测试工作,未免有些小题大做了。
实际上,MO技术最大的好处并不仅仅是测试本身,MO技术将推动设计向着针对接口,可抽换的方向发展。因为MO技术需要做到伪对象和实际对象之间能够平滑的切换,这个能力对于软件架构来说是非常重要的,它充分表现了架构可扩展性和抗变性。
当然,在实际中,MO技术确实需要增加大量的代码。有一些工具能够简化你的工作,例如EasyMock等一些工具。
测试覆盖率
Unit tests drive code quality;
Clover drives unit test quality.
上面的两句话是Clover(http://www.thecortex.net/clover/index.html)的广告词,扣除广告的意味,这句话说得还是非常有道理的。我们的引言中就说明,测试没有办法证明错误不存在。所以,对测试进行分析是有必要的,而测试覆盖率就是最重要的一个指标。
测试覆盖率分析有两种方法:代码覆盖率和分支条件覆盖率。对测试进行覆盖率分析是软件度量的重要方法,也是测试的组成部分,不少的开源项目已经将其纳入到了日构建的过程中。
测试的成本
虽然之前我们讨论了大量激动人心的思路和技术。你可能会热血澎湃的打算在组织中实施测试驱动方法。但是我不得不向你泼冷水了。测试驱动方法的引入不是简单的过程,对一些企业来说,甚至相当难以做到。这是因为以下这几个原因:
工作量的估算方式需要改变。在测试驱动方法中,一个开发人员除了需要编写实现代码,还需要编写测试代码,这将会使得工作量上升,此外,为了自动化测试而对设计的改进还将会需要一定的时间。所以,要求开发人员学习测试驱动方法没有任何的意义,关键是需要为他们留出足够的时间。
项目进度。由于工作量的上升和新知识的使用,项目进度会迅速下落,然后随着开发人员熟练程度的提升和自动化测试的优势才会慢慢回升,如果实施成功,最终的项目速度将会超出实施前,这是完全有可能的。
人员的主动性和勇气。根据我们的经验,不少的组织和开发人员都能够认识到测试驱动的好处,但是往往由于现实环境的原因,导致测试驱动方法的实施无以为继。组织由于项目的时间压力,导致其不敢对测试驱动方法进行推广,往往是浅尝则止。个人由于缺乏足够的耐心和时间,导致其不愿和不敢对设计进行重构,而这恰恰是测试驱动的前提。
测试驱动方法的应用没有那么简单,一个组织如果没有足够的勇气是很难做到的。所以,为什么一开始我们就强调,本文的读者是那些应用较负责,对质量非常敏感的项目的相关人员,其中的一个原因就在于此。
建立测试文化
测试驱动方法不是一个简单的方法论,它也不会和任何的方法论进行竞争。事实上,无论你的组织采用何种方法或过程,都可以从测试驱动中获利。因为它强调的是质量文化。
把测试看作一项核心工作,测试同样需要重构,以及必须的文档。
固定测试的目录组织和包组织。例如,一种较好的组织测试的方法是采用和源代码同样的包名,但处于完全不同的目录中。
使测试成为日创建的核心步骤。
测试是所有人的事情,而不仅是QA的事。
进一步了解
从Junit的网站(http://www.junit.org)上,你可以了解到Junit的大量信息,包括介绍性的文章,Junit的扩展等。
Test-Driven Development Series Part 1 - Overview (http://www.theserverside.com/articles/article.jsp?l=TestDrivenDevelopmentPart1)是一组介绍测试驱动开发的文章,和本文不同,它更侧重于实际代码的编写。