站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Java 专题使用FitNesse进行测试先行开发

使用FitNesse进行测试先行开发

摘要: 使用FitNesse进行测试先行开发
内容: 学习如何利用FitNesse解决开发质量方面的问题


摘要
本文描述了如何使用开源的FitNesse来实现真正的测试先行开发过程,并让客户、需求提报工程师、开发人员、以及测试人员进行协同工作,达到需求更精准、减少需求更改、测试数据与Junit单元测试代码分离的目的,让这一切更简洁、更易于维护。
作者:Stephan Wiesner
译者:陈海青(joson)

在过去的几年里,我在开发测试工作中担任过各种角色,使用过服务器端的JavaScript,Perl,PHP,Struts,Swing以及模型驱动架构等各类技术。尽管项目不同,但是他们有一些共同点:项目结束的时间越晚,就越难以达到客户的真正需求。
每个项目都有一些需求,有的非常详细,有的却只有几页纸,这些需求一般要经历以下三个阶段:
---由客户或者承包人来书写或采纳一些官方的验收标准
---测试者试图根据需求来找出软件中不符合要求的地方
---项目开发完毕进入验收测试, 可是客户突然又提出对软件需求进行补充或变更的要求

最后一个阶段将导致项目发生变化,开发期甚至要超出最后期限,使开发人员的工作压力剧增,从而导致发生更多的错误。Bug的数量将快速增长,系统的质量将下降。听上去是不是很熟悉?

现在让我们看一下在上述的项目开发中发生了哪些错误:客户、开发、测试人员没能协同工作;需求已经确认通过,但是在不同位置的人可能还用不同的需要未考虑。另外,开发者一般会写一些自动测试代码,测试人员也试图进行自动化测试,但是他们往往不能充分协同,许多项目被重复测试,但另一些(经常是更困难的部分)却没能被测试,客户也没参与到测试工作中。本文所介绍的就是通过自动测试与项目需求相结合的方式来解决这些问题的一种方案。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Stephan Wiesner ;joson(作者的blog:http://blog.matrix.org.cn/page/joson)
原文:http://www.javaworld.com/javaworld/jw-02-2006/jw-0220-fitnesse.html
Matrix:http://www.matrix.org.cn/resource/article/44/44507_FitNesse.html
关键字:FitNesse;Test

开始FitNesse

FitNesse是一个增加了可触发Junit 测试等附加功能的wiki程序。如果这些测试能够与业务需求结合起来,就会使业务需求更加清晰。而且,测试数据的组织更有逻辑性。使用FitNesse更重要的是学习隐含在其中的一些思想,某些部分需求可以作为测试的一部分,这意味着,这些需求是可以测试的,或者说是可以进行校验的。

利用FitNesse,开发的工作过程可以这样描述:需求工程师使用FitNesse书写业务需求(取代了一般文档)。他试图尽可能让客户参与其中,当然这并不是每天都能做到的。而测试者在反复研究这些文档,并从第一天起就开始提问各种问题,因为他们考虑问题的方式不同,不是在考虑“软件应该实现些什么”?而是在考虑“怎样才能让软件出错?如何让软件中断运行?”等。开发者更象一个需求工程师,他更想知道“软件必须要完成它的功能是什么”?

测试人员可以更早地开始测试,甚至在需求没有全部完成前,而且可以把测试写进业务需求中,这些测试不仅仅成为需求的一部分,而且也将成为需求评审和验收的重要过程,并具有以下几方面的重要优点:

---客户也会被吸引来开始考虑关于测试的事情,通常他们还会参与到建立测试的工作中来(你也许会吃惊,他们怎么对这些这么感兴趣了。)
---相关规范将更详细、更周密,因为测试总比单纯的文字要准确.
---通过这种方式,可以更清晰明确地了解软件(象一个软件原形,但是功能更多),因此可以更早地考虑真实的运行场景,提供测试数据和测算结果。

最后,需求将提交给开发人员,他的工作要比以前要更容易些,因为需求都附带具体的实例,因而更贴近实际需求,因此减少了被突然改变的机会。下面,就让我们看一下这个过程是如何使开发者工作更轻松的吧。

测试先行的实现

通常情况下,测试先行开发中最困难的是没人愿意花费那么多的时间来写测试,而更愿意去考虑如何让软件工作起来。按照上述的过程,开发者把功能测试看作合同的一部分,他的任务要从“按要求编程,检测并修改”转变为“让测试运行起来”。现在,在确定应该做什么,何时完成,项目的定位等方面,我们有了更好的方法。

并非所有的测试都可以自动进行,并非所有的部分都可进行单元测试。我们通常将测试划分为以下几种类别:
---数据驱动的测试,需要通过单元测试来完成,如计算就是一种典型的例子.
---关键字驱动 (Keyword-driven) 测试,常自动化进行。这是一些系统测试,要求应用程序能够运行,按钮能够被点击,数据可以被键入,而输出的结果中包含规定的值。测试团队一般都能实现这种测试,但是可能开发者更容易完成这些工作。
---手工测试。这类测试适用于或者实现自动化测试的代价太昂贵并且是对出错的要求不高的情况,或者是一些基础功能(如,起始页面不能显示),可以很容易地发现错误的情况。

我是在2004年首次接触FitNesse,我曾经嘲笑过它并扬言它是不可能工作的。把测试写入wiki并自动进行测试,这个主意看起来太荒唐。但是,我错了,FitNesse真的象看起来得那样简单高效。

FitNesse的简单是从安装时就开始体现了,只要完全下载了FitNesse的发行包,并解压缩就可以了。在以下的讨论中假设解压到了c:\fitnesse目录中。

运行C:\fitnesse 下的run.bat(linux 下运行run.sh)来启动FitNesse,FitNesse作为一个web服务运行在80端口上,当然你可以指定端口,如设定81,在运行脚本的首行加上 –p 81 即可,这就是设置的全部工作。现在你可以用http://locahost:81来访问你的FitNesse了。

在本文,我使用windows平台的java版FitNesse,当然,这些例子也可以用于其它版本和其它平台(如Python,.net等)

一些测试

你可以从FitNesse的在线文档提供了一些例子(可以与有名的Junit的货币用例相提并论)开始做起,这些例子非常适合学习如何使用FitNesse,但是它们还不算是解决复杂问题的例子。因此,我将使用一些在我最近项目中的真实的用例。我已经简化了问题、代码,而不是直接取自项目中,并写了一些说明。尽管如此,这些例子还是足够复杂,能够充分展示FitNesse简洁直观的威力
现在,假设我们正在从事一个为一个大型的保险公司开发开发项目,这是一个复杂的基于java的企业级应用。产品将涵盖公司的全部业务,包括客户和合同管理,以及支付业务,在我们的例子里,我们只关注其中的极小的一部分。

在瑞士,父母有权利获得给每个孩子的儿童津贴,经过确认家庭环境后,将根据情况得到相应的津贴。以下是这个需求的简化版本,我们将从一个传统的需求开始,并把它迁移到FitNesse中。

儿童津贴的发放存在几种状态。有关条款规定在孩子出生月份的第一天开始生效,在孩子达到规定年龄、就业或死亡的所在月份的最后一天失效。

按规定在到了12岁所在月份的第一天,就可以领到190瑞士法郎(瑞士官方货币)的补贴。
根据父母全职或兼职工作等情况,依据不同的条款,如表1所示进行分类.


图 1. 儿童津贴资格表.

就业率要根据工作合同来计算,合同需要被确认是有效的,如果有终止日期,还要确认是否在“有效期”内。表2显示了根据孩子年龄的不同等条件,父母可以得到的补贴数量。


图 2. 年龄相关条款

正常情况下,每两年要对款项进行有规律地调整。

第一次看到这些需求,也许会认为这些需求是很明确的,开发人员有能力轻松地实现它,但是,我们真的确定了这些边界条件了吗?该如何测试呢?

边界条件
边界条件是指达到、超过、低于某个给定的输入输出之的情况。经验表明,在边界条件附近的测试比其他测试更有价值,典型的例子就是著名的“只执行一次”的条件跳转和程序段.


场景对于查找例外条件和边界条件有很大帮助,因为这是得到关于商业规则的行业经验的好方法。

场景

对大部分项目而言,是由需求工程师来提供给开发人员的需求规格说明书,然后开始学习了解需求,提问,然后开始设计、编码、测试。再后来,开发人员把软件提供给测试团队,在经过几番改写和修补后,最后交付给客户(客户也许更喜欢考虑提出一些意外的需求)。如果采用了FitNesse,将不会改变这一过程,只是要增加一些测试用例、场景,和测试意愿。

场景在启动测试过程中具有特别的的帮助。以下是一些例子,在回答将支付多少儿童津贴等为题上,就要分清多种情况:
---玛丽亚是单亲家庭,她又两个儿子 (鲍勃, 2岁, 彼得 15岁) ,从事兼职秘书工作 (每周工作20个小时).
---玛丽亚失业了,后来她找到一个每周工作10个小时的商店助手和每周工作5个小时的临时照顾幼儿的工作
---保罗和拉瑞(Lara)有一个17岁的女儿丽莎(Lisa),她是一个残疾人,还有一个儿子弗兰克,18岁,还在上大学。
即使是仅谈论一下这些场景,也有助于测试工作的开展,哪怕是软件中手工运行这些例子,也几乎可以确信会发现程序的遗漏,难道因为没有原型就不做这些吗?为什么不做呢?

关键字驱动(Keyword-driven)的测试

关键字驱动的测试常用于模拟原型,FirNesse允许定义关键字驱动的测试类型(详见“完全的数据驱动自动化测试”(“Totally Data-Driven Automated Testing”))甚至在没有软件支持的情况下(不能自动运行),运行基于关键字驱动的测试也会很有好处。


图 3. ActionFixture 测试

图3展示的是关键自驱动测试的示例。第一列描述的是来自FitNesse的关键字,第二列描述的是java类的方法(开发者根据这里的描述,在Java程序使用这些名字来命名)。第三列描述的是来自第二列的方法所产生的数据。最后一行演示了测试失败的情形(测试通过为绿色)。正如你所看见的那样,找出错误是很容易的。

这会使人很容易甚至很乐意去建立这些测试。测试者不必具备编程技能,经过简短介绍后,客户也会很容易的读懂。
用这种方法定义的测试,几乎就是业务需求,并具备传统测试用例没有的重要优势, 甚至不必去自动生成也有很多优势:
---测试内容很容易获得,不必特别训练就可以轻松完成。
---与其改变需求,到不如先改变测试,这样反而更直观(这与使用某些工具的情况完全不同)
---在确定新需求或修改需求时,可以随时运行测试来看看需要修正哪些内容。

为自动运行测试,需要建立一个简单的软件层,用于代表真实的测试代码。这些测试在自动化测试GUI操作时特别有用。我曾开发过一个基于HTTPUnit的自动测试web页

这里就是FitNesse自动运行的代码:
package stephanwiesner.javaworld;

import fit.ColumnFixture;

public class ChildAllowanceFixture extends ColumnFixture
{
public void personButton() {
System.out.println("pressing person button");
}
public void securityNumber(int number) {
System.out.println("entering securityNumber " + number);
}
public int childAllowance() {
System.out.println("calculating child allowance");
return 190;
}
[...]
}


在FitNesse中的运行结果如图4所示,很有助于调试。与Junit相比,Junit抑制了调试信息的输出,但我认为在自动Web测试中这些输出绝对是有必要的。


图 4. 标准输出

在测试基于Web的应用时,错误也将被包含在FitNesse页面里,并被显示出来,这是的条施工作比使用log文件更容易。

数据驱动的测试

关键字驱动的测试是GUI自动测试的最佳选择,同样,数据驱动测试也是各类计算类项的首选测试方式。如果你曾经写过单元测试,那么测试中什么是最令人厌倦的呢?也许,你会认为是数据。你的测试要进行数据填充,但是数据经常改变,使维护工作变成了可怕的恶梦,测试不同的组合,需要不同的数据,这也许会使你的测试工作变得日益复杂,变成了”丑陋的怪兽”。

使用数据驱动的测试,数据将从代码中分离。一般情况下,建立几种类型的表格,存储为CSV文件,由测试代码来读取。如果再使用FitNesse,我们就可以更便利地进行存储、改变,以及访问这些测试数据。

现在,让我们把图2种的表格转变成一个测试吧。首先,将表格拷贝到Excel中,进行修改,然后使用FitNesse的EXCEL导入功能输入。这是一个有用的功能,因为在FitNesse中,表越大越难以维护。输入的结果如图5所示。每一行表示一个测试,有问号的列表示需要写Java方法,没有问号的行表示输入的测试数据。


图 5. 数据驱动测试表Data-driven test table.

再重复一次,在阅读了业务需求后,这种测试就非常容易理解了。如果你曾经担心如何在你的测试用例里的进行等值测试的话,那就看看这里吧。最后一行大概是最重要的,因为需求并没有说明如何处理非法数据,或者处理20岁以上的孩子但仍在上学等情况,这里的关键字“error”表明需要处理异常了。

常见问题解答

这里讨论使用FitNesse在需求、测试和开发过程中遇到的未解决的问题的解决办法,以下是部分答案。

手工测试会遇到什么问题?
手工测试者最常做的就是重复的手工回归测试,不但代价昂贵,而且容易出错。自动化测试可以减少但不能消除这种工作的工作量。测试者可以有更多的时间去从事更有趣的测试,例如在应用程序在复杂的场景下的不同处理等,尽管测试就是要花费更长的时间找到错误,但比不意味着因此而要付出更高的代价.

用户验收测试不再有必要了吗?
也许有人会这样认为。如果客户在需求中定义了所有的测试,当所有测试测试都变绿(通过了)时,软件就“完工”了。我的实践经验是,客户仍会提出附加的需求来,尽管如此,他们的需求变化会更少,并更容易地结合实际变化进行分类(同时也会带来追加的款项)。

在FitNesse书写比Word documents更有优势吗?
甚至在没用测试的情况下,使用wiki 来写业务需求也能带给你更多好处。文档可以自动用在Web 服务器上,可以被多人并发访问,可以全文检索,可以被链接。我发现还有以下两个特点:
---更容易建立数据字典,并链接上明细内容。更重要的是,具有查找被引用位置("where-used")的功能。 如果修改某个细目,就会立即找到是否被引用过,以避免发生冲突.
---由于Bugzilla (或者 Jira,二者均为缺陷跟踪系统,译者注) 是基于Web的, 我们可以方便地在两个系统之间互相链接文档(artifacts) (bugs, 任务, 讨论等),便于提高工作效率。

FitNesse 允许按照确定的顺序运行测试,而JUnit 禁止这样做,你有何体验?
使用JUnit,需要对同一个对象分别建立许多setUp() 和tearDown() 方法. 在某些情况下,可以在一个FitNesse页面里放上所有的相关测试,并能够给定执行顺序. 因此,第一个测试可以建立一个”人”,下一个测试可以对它进行修改,最后一个可以进行删除操作.如果第一个测试失败了,而且那个”人”没有被建立,这该怎么办?没问题,因为第一个测试失败了,随意在把它修正好之前,我是不会继续的. 我一般不会经常使用这种顺序执行方式,但有时它会给我带来很大方便.

FitNesse 可以用于大项目吗?
大部分开发者会明白,FitNesse的优势就是简单易用。但没有技术背景的人,如果不使用自动检查特性以及所见即所得风格的编辑器,工作起来会很困难。一个简单的例子就是FitNesse 只有允许使用三种类型的标题, 但现在我们需要五种,这就需要我们就可以对FitNesse 进行扩充.

FitNesse 不包含输出为类似PDF文件的功能。我曾经使用过长度达到60页的Word文档,尽管编辑起来有些困难,但是可以轻松打印出来. 但是当我把它转到FitNesse里后,我就难以打印了,因为被分解为几个不同的页面,最后不得不通过编程解决.

FitNesse 具有一个简单的机制可以显示那些文档被修改了,被谁修改了,但不能显示修改了什么, 权限管理有局限。使用FitNesse重开一个项目要比改写一个运行的项目更容易.对中小型项目而言,我极力推荐FitNesse. 大项目也可以使用,但是进行更仔细的评估后再实施会更好.

结论
我认为FitNesse是需求分析的好工具,对测试优先开发很有帮助,它是很有利用价值的。我被迫花费几个周来适应FitNesse及其处理过程,我的项目目前尚未完成,但是我已经看到了需求质量和软件质量均得到明显提高,而对测试者而言,工作起来会获得更多的乐趣 。

关于作者
Stephan Wiesner 是瑞士卢赛恩Löwenfels Partner AG的测试管理人员。他是“Learning Jakarta Struts 1.2”(伽利略出版社,德语版,ISBN:3898426130;英语版:ISBN: 190481154X)的作者,” Java der Code”(由德国MITP出版社发行,ISBN: 3826614623)。另外还在德文杂志上写过大量的与java相关的文章。他有着丰富的开发和测试经验,是一位ISTQB认证的测试管理员和认证的Scrum主管(ScrumMaster,Scrum是一种敏捷开发方式,译者注)。

陈海青(joson),本文译者,生活在中国的山东省烟台市,先后从事软件开发、数据库管理、系统管理等工作,2001年获得高级程序员资格。

相关资源
---Matrix-Java和中间件社区:http://www.matrix.org.cn
---“完全的数据驱动自动测试”("Totally Data-Driven Automated Testing" ),作者Keith Zambelich ,详细阐述了数据驱动和关键字驱动的测试: http://www.sqa-test.com/w_paper1.html
---以下站点提供了更多的关于优化FitNesse的内容: http://fitnesse.testmanager.info
---官方的FitNesse网站,经常更新: http://fitnesse.org/
---下在完整的FitNesse发行版: http://fitnesse.org/FitNesse.DownLoad
---部分资料可以在译者的站点找到:http://www.chq.name/ Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd
↑返回目录
前一篇: 使用UML编写Java 设计模式例子 FactoryMethod Pattern
后一篇: java设计模式之Proxy(代理)