浅谈Fitnesse在Web项目中的应用

1      什么是Fit

Fit是Framework of Integrated Testing 的缩写,是一个致力于提供一种简单易用的“验收测试(Acceptance Test)”途径的通用的测试框架,由Ward Cunningham开发,是一个开源的框架。验收测试(也称功能测试)是单元测试和组合测试的补充,通常是手工完成的,但使用Fit能够帮助我们使用编程语言来编写测试用例,并进行自动化的验收测试。

2      Fit如何工作

Fit是一个框架,也是一个软件,它通过解析HTML表格,每一个表格由一个“Fixture”类来解释,Fixture通过执行被测试的代码来检查HTML表格中所列出的测试用例。除了HTML表格以外,整个HTML文档还可以写上对测试用例的详细描述,可以包括对业务需求的测试等等,以达到清晰描述业务和测试用例的目的。而Fitnesse是一个基于Fit的测试环境,是Fit的Wiki界面,是一个Wiki服务器。它提供一个简单易用的,基于Web在线编辑业务需求、测试用例的前端工具,方便开发、测试、需求人员乃至用户进行沟通和交流。执行验收测试的是FIT Server,FIT Server的输入接口为带表格的HTML文档。当我们点击“test”链接提交一个验收测试的时候,FitNesse将网页发送到FIT Server,FIT Server读取网页中的表格,调用相应的测试程序,然后将结果返回给FitNesse。

Fit框架提供三种不同的Fixture类,分别是:ColumnFixture、RowFixture和ActionFixture,它们分别适用于不同的测试情况,Fixture类主要完成对HTML表格的解析和处理,实际的测试用例可以通过继承这些Fixture类,并覆盖其中的方法来实现。下面将简要说明一下这三个Fixture的使用情况。

ColumnFixture主要适用于列表,比如有一个计算成绩等级的功能,需要对不同的分数进行统计和划分,就可以使用ColumnFixture来进行模拟,表格的样式可以是这样:

ScoreCalculatorFixture

Score

GetLevel()

90

A

80

B

RowFixture主要适用于行式表格,它比较适用于测试一个返回很多列表数据的功能,RowFixture在使用时,通过覆盖GetTargetClass和Query方法来提供需要检查的数据,例如有一个查询订单的功能,用于返回所下订单的产品信息,表格的样式可以是这样:

OrderListFixture

LineNumber

Product

CostPrice

Purchase

1

Cat

2,000

1

2

Dog

3,500

2

ActionFixture用于测试一些需要很多操作步骤才能完成的功能,比如目前流行的向导式功能。与ColumnFixture和RowFixture不同,编写用于ActionFixture的测试类不需要直接继承ActionFixture,但要继承Fixture类。ActionFixture有几个主要命令,Start:创建一个与Fixture相关联的测试类;Enter:为一个参数进行赋值;Press:调用一个方法;Check:检查返回值。

3      实际应用

3.1             测试背景

笔者目前正在测试的系统是一个赛马网上投注系统,这是一个B/S架构的系统,系统结构如图3-1-1所示: 用户通过操作Web Client调用远程Web Server API来完成业务功能,并提供友好的界面展现,从架构上系统真正地做到了业务逻辑与表示层逻辑的分离,降低了表示层和业务逻辑的耦合度。然而,这种架构同时也为系统的测试工作带来了一定的难度:

一、     从项目进度和质量管理考虑,我们的测试不允许等到底层与界面都开发完成并有效组合起来后再进行,于是有了测试先行的要求。

二、     完整的功能常常需要组合好几个步骤,对于只有底层API的无GUI的系统很难进行测试。

三、     底层API返回的数据格式经较利于计算机处理,而不太易于阅读,对于一般的测试人员和用户来说都是一项很艰巨且易于出错的任务。

通过一段时间的研究,我们了解到Fit是一个非常适合进行“测试先行”及”自动化测试”的测试框架。Fit的优点在于它提供了一个简单易用的测试框架,利用Fit的ColumnFixture、RowFixture和ActionFixture等,可以快速地建立符合要求的测试用例,满足关键字驱动测试、数据驱动测试的要求。加上Fit的Fixture是基于HTML表格,并且有了Fitnesse这样易于沟通交流的Wiki界面,有利于项目组成员与用户之间的沟通。友好的测试操作界面也降低了对测试人员的要求,除此之外Fit还是一个免费开源的,易于在其基础上进行扩展。因此我们选择Fit做为我们的测试平台,来进行系统的先行测试和底层API的自动化功能测试。

3-1-1

3.2             一些测试实例

在这里,我们以赛马投注系统中的一些功能测试作为测试实际来进行介绍。

实例一:赛马投注系统中,用户管理子系统有一个密码取回模块retrievePassword,其用来实现用户重设密码的功能,用户取回密码时需要提供以下信息:用户Id (UserId),生日(Birthday ),邮箱 (Email)。Web Server端的API根据接收到的UserId、Birthday、Email信息检索数据库,判断其正确与否并以XML格式返回密码取回状态。

n         提出问题:

在此我们的测试对像是Web Server端的API,检验API处理数据的正确性。场景中API具有以下特点:

1、 API运行在远程的WebServer

2、 API参数通过HTTP的POST或GET方法进行传送

3、 API返回的只有业务数据,并且数据使用XML格式进行组织,且XML字段的定义遵守预先定义的DTD(或Schema)。

n         解决思路:

根据对前面场景的分析,采用Fitnesse对API进行测试,我们还需要了解几个方面的技术:

1、 HTTP协议,了解如何进行GET和POST,包括简单的Cookie的处理

2、 XML解析,能够对XML节点树进行遍历,获取指定节点的文本或属性

3、 FIT Fixture,了解FIT Fixture和HTML表格的组织,并能够使用ColumnFixture来进行测试数据的准备和测试

对于以上提到的前两个技术,在.net框架中都有很好的支持,简单地提一下,主要使用的类为:System.Web.HttpWebRequest、System.Web.HttpWebResponse及System.Xml名称空间下的对应类。本文采用.net来实现对这个功能的测试。(Fitnesse也支持java开发)

3-2-1

接下来,我们需要对功能进行分析,写出对应的测试用例,并根据测试用例完成。图3-2-1为访问 retrievePassword时Web Server 返回给 Web Client的XML数据结构(Schema)。对本API的测试用例设计如下表:(假设UserId为test01,Birthday和Email分别是:8/7/1947和[email protected]

编号

用例描述

输入设计

预期输出

1

输入正确的UserId,Birthday, Email,访问API。

UserId=test01

Birthday=8/7/1947

[email protected]

返回

Code = 0

(说明:密码已发送到您的邮箱)

2

输入错误的 UserId,Birthday, Email,访问API。

UserID=test01

Birthday=1/1/1920

[email protected]

返回

Code = -1

(说明:用户名不存在)

2

输入正确的UserId,错误的 Birthday,错误的Email,访问API。

UserID=test01

Birthday=1/1/1920

[email protected]

返回

Code = -2

(说明:生日、邮箱不符)

3

输入正确的UserId,错误的Birthday,正确的Email, 访问API。

UserId=test01

Birthday=1/1/1920

[email protected]

返回

Code = -3

(说明:生日不符)

4

输入正确的UserId,正确的Birthday,错误的Email

UserId=test01

Birthday=8/7/1947

Email= [email protected]

返回

Code = -4

(说明邮箱不符)

n         实例代码:

首先,需要在Fitnesse上创建一个测试页面,输入测试输入输出数据,其内容如下:

!define COMMAND_PATTERN {%m %p}

!define TEST_RUNNER {dotnet/2.0/FitServer.exe}

!define PATH_SEPARATOR {;}

!path dotnet/2.0/*.dll

!path FitnesseRoot/ API_Test.dll

!2 RetrievePassword

|!-Test_RetrievePasswordResponse.TestRetrievePassword-!|!-http://www.eqa.kingcontest.com/platform/webservice/4.1/retrievePassword/retrievePassword.aspx-!|

|!-UserId-!|Birthday|Email|Check()|Code?|Message?|

|test01|8/7/1947|[email protected]||0||

|testX|8/7/1947| [email protected] ||-1||

|test01|2/2/2222|[email protected]||-2||

|test01|2/2/2222| [email protected] ||-3||

|test01|8/7/1947|[email protected]||-4||

接下来,我们模拟访问API retrievePassword,在这里,我们要利用Fit的ColumnFixture来实现,关键实现代码如下所示。代码中,使用了一个ColumnFixture 来将表格中的列映射到类TestRetrivePassword中的变量和方法上。表中前三列提供了输入信息,对应于类中的变量,第四列则对应类中的Check方法,它负责从API取回数据并对取回的Code值与第五列的Code期望值进行比较,并将密码取回状态的详细信息输出到第六列中。

using System;

using System.Text;

using System.Net;

using System.Xml;

using System.IO;

using fit; //导入fit名称空间

namespace Test_API

{

    public class TestRetrievePassword : ColumnFixture

    {

        public string UserId;

        public string Birthday;

        public string Email;

        public string Message;

        public int Code;

        public void Check()

        {

            Code = 0;

            Message = "";

            XmlDocument xmlDoc = new XmlDocument();

            string url = string.Format("{0}?UserId={1}&Birthday={2}&Email={3}", Args[0], UserId, Birthday, Email);

            xmlDoc.Load(url);

XmlNode node = xmlDoc.DocumentElement.SelectSingleNode("descendant::retrievePasswordResponse");

                if (node == null)

                {

                    Code = 1;

                    Message = "xml error.";

                }

                if (node.Attributes["code"] != null)

                    Code = Convert.ToInt32(node.Attributes["code"].Value);

                if (node.Attributes["message"] != null)

                    Message = node.Attributes["message"].Value;           

        }

}

}

最后,点击页面左边菜单的Test链接执行测试,测试结果如图3-2-2:

3-2-2

从运行结果中不难看出,前四行Code单元都为绿色,说明Code的值与期望值一致,测试通过,第五行中Code单元为红色说明测试失败,期望的值是-4,而实际测试输出却是-1。

这里简单提一下,!define和!classpath是fit预定义的关键字,用于设置系统变量和路径,用!--!来使fitnesse忽略对链接的处理,特别是当类名由使用大写字母开头时,必须使用这种方式来进行转义。

实例二:在进行赌马投注的过程中,Web Client需要向用户展示各个赛事(Contest)的所有比赛场次(Event)信息。 API getEventsForContest在接收到用户查看某ContestId的Event请求时,根据ContestId检索出的所有场次(Event)信息并以XML格式返回。XML结构图如图3-2-3:

3-2-3

从结构中,可以了解到,远程接口在根据当前ContestId 进行数据收集后应返回此ContestId 所对应的Contest 下所有的Event的详细信息。

假设已有ContestId=102,其底下有6场比赛,真实信息分下表:

contestId

eventId

shortDescription

startTime

status

eventStatus

102

XAD

FB TRACK A - XAD

2007/08/14 19:32:00 UTC

Current

Open

102

XBD

FB TRACK B - XBD

2007/08/14 19:32:00 UTC

Current

Open

102

XCD

FB TRACK C - XCD

2007/08/14 19:32:00 UTC

Current

Open

102

XDD

FB TRACK D - XDD

2007/08/14 19:33:00 UTC

Current

Open

102

XED

FB TRACK E - XE2

2007/07/17 23:00:00 UTC

Current

Closed

102

XFD

FB TRACK F - XFD

2007/08/14 19:33:00 UTC

Current

Open

n         部分实例代码:

现在我们要测试API getEventsForContest 返回的这些event信息的正确性,模拟访getEventsForContest的关键代码如下所示:

   public class TestEvent : RowFixture

    {

        public override Type GetTargetClass()

        {

            return typeof(customEvent);

        }

        public override object[] Query()

        {

            ArrayList list = new ArrayList();

            XmlDocument xmlDoc = new XmlDocument();

string Url = Args[0];

          xmlDoc.Load(Url);

            XmlNode root = xmlDoc.DocumentElement;

            XmlNodeList eventList = root.SelectNodes("descendant::event");

            foreach (XmlNode event in eventList)

            {

                customEvent custom = new customEvent();

                if (event.ParentNode != null)

                                     custom.contestId = event.ParentNode.Attributes["contestId"].Value;

                if (event.Attributes["eventId"] != null)

                                     custom.eventId = event.Attributes["eventId"].Value;

                if (event.Attributes["shortDescription"] != null)

                                     custom.shortDescription = event.Attributes["shortDescription"].Value;

                if (event.Attributes["startTime"] != null)

                                     custom.startTime = event.Attributes["startTime"].Value;

               if (event.Attributes["status"] != null)

                                     custom.status = event.Attributes["status"].Value;

                if (event.Attributes["eventStatus"] != null)

                                     custom.eventStatus = event.Attributes["eventStatus"].Value;

            }

            return list.ToArray();

        }

public class customEvent

    {

        public string contestId;

        ………………

    }

测试执行结果如图3-2-4:

3-2-4

以上介绍了如何利用fitnesse进行两个API的自动化测试,代码很简单,却非常有效。从实例中不难发现,ColumnFixture非常适用于利用多组输入数据来验证某一功能不同分支的处理结果,从而很好地实现了测试用例的可配置和可重复性。当然,除了RowFixture, ColumnFixture外,还有适用于关键字驱动的测试的ActionFixture等,这里就不再详细说明。

另外,在Fitnesse中,测试表会越来越大,难以维护,Fit提供了一个很有用的功能,就是可以将输入数据和期望数据存储到Excel有中,然后通过使用Fit的Excel导入功能输入。

你可能感兴趣的:(Web,server,api,String,测试,email)