【测试分析】基于状态的测试——实战
◆版权声明:本文出自胖喵~的博客,转载必须注明出处。
转载请注明出处:http://www.cnblogs.com/by-dream/p/5367372.html
前言
最近一直在学习测试理论的一些东西,主要是以下内容:
等价类划分、边界值
测试业务流程
从用例到测试用例
基于状态的测试
决策表和决策树
初步比较
组合测试
数据周期测试
语法测试
学习了这些建模方法后,决定在其中一个模块中进行应用一下。这里选择的是**项目:
首先介绍一下此App的业务的吧:
调研显示,几乎人人都有闲置物品,而超过一半的用户倾向于让闲置物品放在一边不作处理。导致这种局面的原因,是因为大部分用户没有闲余时间及精力再去倒卖闲置物品,而小部分用户则是不知道倒卖二手商品的渠道。闲贝推出二手交易APP,迎合了很多买家变“闲”为“现”的想法,也响应了社会低碳生活的号召。
因此这款App就孕育而生。
那么做为一个测试我们需要关注这个App的哪些内容呢?
当然是它的买卖系统,可以说这是这款App的核心了。
初探
使用过淘宝的用户应该都知道,在买卖的过程中,存在这很多状态转化,这些状态的转化相对于买家和卖家来说是不一样的,因此针对 “状态变化” 这样的情况,我选择了基于状态的测试的方法,而买家和卖家的异同,我决定将买家和卖家分开。因此首先我对买家和卖家生成了两个状态图。
1、完成状态图
卖家:
买家:
从图中不难看出,买家相对来说没有卖家那么复杂,因此这里就以买家为例。
2、两两生成最简转化对:
有了状态图之后,我们从状态V1开始,生成转化对,每一个转化对只关联两个状态,如下图所示:
当然我们也可以生成这样的表,生成如下这样的表到下一步会更方便一点:
3、生成转化对:
根据上面的一一对应的转化表,我们可以生成转化对:
注意这里面转化对没有包含V1、v5、v7,因为V1是起始态,V5和V7是终止态,对于转化对来说,他们要求是有输入,并且有输出的,而起始和终止状态是不完全的,因此这里没有列入进来。
4、生成最终的转化表
生成的方法是从上面的表中的following开始,然后每个终点的字母可以看它是否还是其他following的起点,如果是那么就继续往下,直到把所有的following中的条件走完。这里举一个例子:
生成的 a->c->e->f ,我们可以去状态图中查阅,走的流程是如下的流程:
因此就可以得出:V1->V2->V3->V4->V5
整理所有的线路,最终得出的转化表就是:
由于是首次学习,首次应用,因此难免会有些问题,我们在小组讨论的时候发现了一些可能被忽略的点:比如买家从V2(待付款)到V3(代收货)这个过程中,如果卖家关闭了订单,会发生什么呢?
因此说明在划分状态对象的时候,将太多的元素加入到了里面,这里应该将用户的操作和订单区分开。有了这个思路,我针对订单重新画了一个状态图:
改进
根据订单这个元素,重新画的图:
关注一下前几个状态。
发现...
和开发碰,拿到返回码:
物品和订单融合在一起导致的。
单独的订单:
由于之前已经实践过一次了,所以这次直接生成转化对:
V2 | 输入 | d | l | ||||
输出 | e | i | j | ||||
状态流 | d->e | l->e | |||||
d->i | l->i | ||||||
d->j | l->j | ||||||
V3 | 输入 | i | n | ||||
输出 | m | l | n | o | p | ||
状态流 | i->m | n->m | |||||
i->l | n->l | ||||||
i->n | n->n | ||||||
i->o | n->o | ||||||
i->p | n->p | ||||||
V4 | 输入 | e | m | r | ab | ac | |
输出 | q | f | h | ||||
状态流 | e->q | m->q | r->q | ab->q | ac->q | ||
e->f | m->f | r->f | ab->f | ac->f | |||
e->h | m->h | r->h | ab->h | ac->h | |||
V5 | 输入 | q | s | aa | |||
输出 | r | s | t | u | x | y | |
状态流 | q->r | s->r | aa->r | ||||
q->s | s->s | aa->s | |||||
q->t | s->t | aa->t | |||||
q->u | s->u | aa->u | |||||
q->x | s->x | aa->x | |||||
q->y | s->y | aa->y | |||||
V6 | 输入 | y | |||||
输出 | aa | ab | ac | z | w | ||
状态流 | y->aa | ||||||
y->ab | |||||||
y->ac | |||||||
y->z | |||||||
y->w | |||||||
V7 | 输入 | f | h | ||||
输出 | g | ||||||
状态流 | f->g | h->g | |||||
V11 | 输入 | x | z | ||||
输出 | v | ||||||
状态流 | x->v | z->v |
转化对和转化表在生成的之间,可以先生成一个中间产物。
例如我们先将转化对中的进行标识:
生成中间产物:
d | e | q | r | f | g |
我们可以在图上标识一下:
同理,第二个:
最终生成的一个:
将此表对应到转化表中:
用前面的方法,看我们的状态图:
可以看到 V1->V8没有覆盖,因此再增加这三条用例即可。
a、b、c
待续.....
附录
NModel是基础状态测试中常用的一个工具,它可以在我们列出对象的状态和执行的动作之后,自动帮我们构建状态图,并且还可以生成用例。
下面我说一下NModel使用的具体步骤:
1、下载NModel:
下载地址 http://nmodel.codeplex.com/ ,NModel的官方网站上,点击 “download” 即可下载。
2、下载 GLEE:
地址:http://research.microsoft.com/en-us/downloads/f1303e46-965f-401a-87c3-34e1331d32c5/default.aspx 该工具辅助NModel最终画图用的。点击Download即可:
3、复制dll
两个软件安装完成之后,将 ..\GLEE\bin下的dll 拷贝到 ..\NModel\bin 下:
4、安装 .NET 3.5
这里去自己电脑的 C:\Windows\Microsoft.NET\Framework\v3.5 目录下:
我本地安装了vs2012,因此我编译没有使用这个,而是在vs工程里面直接编译。
5、下载demo
起初我们可能无法自己写一个C#的程序,因此需要一个demo来参考
6、编译demo
这里我没有使用命令行,我用vs直接打开demo工程,然后使用vs进行编译,编译的结果为dll
附代码:
using System.Collections.Generic; using NModel; using NModel.Attributes; using NModel.Execution; //using NModel.Algorithms; using System; using System.Text; namespace BookMarks { public enum OrderNum { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 } public enum OrderDesc { 初始状态, 代发货, 发货前退款, 代收货, 发货后退款, 发货后拒绝退款, 确认收货, 支付前关闭, 支付后关闭, 完成评价, 平台介入 } public static class BookMarkShow { public static OrderDesc Translation(OrderNum ordernum) { OrderDesc orderdesc = OrderDesc.初始状态; switch(ordernum) { case OrderNum.v1: orderdesc = OrderDesc.初始状态; break; case OrderNum.v2: orderdesc = OrderDesc.代发货; break; case OrderNum.v3: orderdesc = OrderDesc.发货前退款; break; case OrderNum.v4: orderdesc = OrderDesc.代收货; break; case OrderNum.v5: orderdesc = OrderDesc.发货后退款; break; case OrderNum.v6: orderdesc = OrderDesc.发货后拒绝退款; break; case OrderNum.v7: orderdesc = OrderDesc.确认收货; break; case OrderNum.v8: orderdesc = OrderDesc.支付前关闭; break; case OrderNum.v9: orderdesc = OrderDesc.支付后关闭; break; case OrderNum.v10: orderdesc = OrderDesc.完成评价; break; case OrderNum.v11: orderdesc = OrderDesc.平台介入; break; } return orderdesc; } public static void SetOrderState(OrderNum ordernum) { WebSiteModel.ordernum = ordernum; WebSiteModel.orderdesc = Translation(ordernum); } [Feature("diyibu")] public static class diyibu { [Action] // a public static void 下单后买家关闭() { BookMarkShow.SetOrderState(OrderNum.v8); } public static bool 下单后买家关闭Enabled() { return (WebSiteModel.ordernum == OrderNum.v1); } [Action] // b public static void 下单后卖家关闭() { BookMarkShow.SetOrderState(OrderNum.v8); } public static bool 下单后卖家关闭Enabled() { return (WebSiteModel.ordernum == OrderNum.v1); } [Action] // c public static void 下单后卖家超时未处理订单关闭() { BookMarkShow.SetOrderState(OrderNum.v8); } public static bool 下单后卖家超时未处理订单关闭Enabled() { return (WebSiteModel.ordernum == OrderNum.v1); } [Action] // d public static void 买家支付() { BookMarkShow.SetOrderState(OrderNum.v2); } public static bool 买家支付Enabled() { return (WebSiteModel.ordernum == OrderNum.v1); } [Action] // e public static void 卖家发货() { BookMarkShow.SetOrderState(OrderNum.v4); } public static bool 卖家发货Enabled() { return (WebSiteModel.ordernum == OrderNum.v2); } [Action] // f public static void 买家确认收货() { BookMarkShow.SetOrderState(OrderNum.v7); } public static bool 买家确认收货Enabled() { return (WebSiteModel.ordernum == OrderNum.v4); } [Action] // h public static void 买家超时未处理收货() { BookMarkShow.SetOrderState(OrderNum.v7); } public static bool 买家超时未处理收货Enabled() { return (WebSiteModel.ordernum == OrderNum.v4); } [Action] // g public static void 买家完成评价() { BookMarkShow.SetOrderState(OrderNum.v10); } public static bool 买家完成评价Enabled() { return (WebSiteModel.ordernum == OrderNum.v7); } [Action] // i public static void 发货前买家发起退款() { BookMarkShow.SetOrderState(OrderNum.v3); } public static bool 发货前买家发起退款Enabled() { return (WebSiteModel.ordernum == OrderNum.v2); } [Action] // j public static void 发货前卖家关闭订单() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 发货前卖家关闭订单Enabled() { return (WebSiteModel.ordernum == OrderNum.v2); } [Action] // l public static void 买家撤销发货前退款申请() { BookMarkShow.SetOrderState(OrderNum.v2); } public static bool 买家撤销发货前退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v3); } [Action] // m public static void 卖家拒绝发货前退款() { BookMarkShow.SetOrderState(OrderNum.v4); } public static bool 卖家拒绝发货前退款Enabled() { return (WebSiteModel.ordernum == OrderNum.v3); } [Action] // n public static void 买家编辑发货前申请退款() { BookMarkShow.SetOrderState(OrderNum.v3); } public static bool 买家编辑发货前申请退款Enabled() { return (WebSiteModel.ordernum == OrderNum.v3); } [Action] // o public static void 卖家超时未处理发货前退款申请() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 卖家超时未处理发货前退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v3); } [Action] // p public static void 卖家同意发货前退款申请() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 卖家同意发货前退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v3); } [Action] // q public static void 发货后买家发起退款() { BookMarkShow.SetOrderState(OrderNum.v5); } public static bool 发货后买家发起退款Enabled() { return (WebSiteModel.ordernum == OrderNum.v4); } [Action] // r public static void 买家撤销发货后退款申请() { BookMarkShow.SetOrderState(OrderNum.v4); } public static bool 买家撤销发货后退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v5); } [Action] // s public static void 买家编辑发货后退款申请() { BookMarkShow.SetOrderState(OrderNum.v5); } public static bool 买家编辑发货后退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v5); } [Action] // t public static void 卖家同意发货后退款申请() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 卖家同意发货后退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v5); } [Action] // u public static void 卖家超时未处理发货后退款申请() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 卖家超时未处理发货后退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v5); } [Action] // v public static void 仲裁结束() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 仲裁结束Enabled() { return (WebSiteModel.ordernum == OrderNum.v11); } [Action] // w public static void 卖家又同意退款申请() { BookMarkShow.SetOrderState(OrderNum.v9); } public static bool 卖家又同意退款申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v6); } [Action] // x public static void 卖家申诉() { BookMarkShow.SetOrderState(OrderNum.v11); } public static bool 卖家申诉Enabled() { return (WebSiteModel.ordernum == OrderNum.v5); } [Action] // y public static void 卖家拒绝了发货后退款() { BookMarkShow.SetOrderState(OrderNum.v6); } public static bool 卖家拒绝了发货后退款Enabled() { return (WebSiteModel.ordernum == OrderNum.v5); } [Action] // z public static void 买家申诉() { BookMarkShow.SetOrderState(OrderNum.v11); } public static bool 买家申诉Enabled() { return (WebSiteModel.ordernum == OrderNum.v6); } [Action] // aa public static void 买家修改发货后申请内容() { BookMarkShow.SetOrderState(OrderNum.v5); } public static bool 买家修改发货后申请内容Enabled() { return (WebSiteModel.ordernum == OrderNum.v6); } [Action] // ab public static void 买家超时未处理拒绝退款() { BookMarkShow.SetOrderState(OrderNum.v4); } public static bool 买家超时未处理拒绝退款Enabled() { return (WebSiteModel.ordernum == OrderNum.v6); } [Action] // ac public static void 拒绝退款后买家撤销申请() { BookMarkShow.SetOrderState(OrderNum.v4); } public static bool 拒绝退款后买家撤销申请Enabled() { return (WebSiteModel.ordernum == OrderNum.v6); } } } public class WebSiteModel { public static OrderNum ordernum = OrderNum.v1; public static OrderDesc orderdesc = OrderDesc.初始状态; public static ModelProgram CreateLoginModel() { return new LibraryModelProgram(typeof(WebSiteModel).Assembly, "BookMarks", new Set<string>("diyibu")); } } }
7、生成状态图:
命令为:mpv.exe /r:case\BookMarks.dll BookMarks.WebSiteModel.CreateLoginModel
鼠标放到每一根线上会高亮显示。
放到节点上,会显示当前的状态。
8、生成用例
自动生成用例 otg.exe /r:case\BookMarks.dll BookMarks.WebSiteModel.CreateLoginModel /file:TestSuit.txt
TestSuite(
TestCase(
买家支付(),
发货前卖家关闭订单()
),
TestCase(
买家支付(),
发货前买家发起退款(),
卖家拒绝发货前退款(),
买家确认收货()
),
TestCase(
买家支付(),
卖家发货(),
发货后买家发起退款(),
卖家拒绝了发货后退款(),
买家修改发货后申请内容(),
卖家超时未处理发货后退款申请()
),
TestCase(
买家支付(),
发货前买家发起退款(),
卖家同意发货前退款申请()
),
TestCase(
买家支付(),
发货前买家发起退款(),
买家撤销发货前退款申请(),
发货前买家发起退款(),
买家编辑发货前申请退款(),
卖家超时未处理发货前退款申请()
),
TestCase(
下单后卖家超时未处理订单关闭()
),
TestCase(
买家支付(),
卖家发货(),
发货后买家发起退款(),
卖家申诉(),
仲裁结束()
),
TestCase(
下单后卖家关闭()
),
TestCase(
买家支付(),
卖家发货(),
发货后买家发起退款(),
卖家拒绝了发货后退款(),
拒绝退款后买家撤销申请(),
发货后买家发起退款(),
买家编辑发货后退款申请(),
买家撤销发货后退款申请(),
发货后买家发起退款(),
卖家拒绝了发货后退款(),
买家超时未处理拒绝退款(),
发货后买家发起退款(),
卖家拒绝了发货后退款(),
卖家又同意退款申请()
),
TestCase(
下单后买家关闭()
),
TestCase(
买家支付(),
卖家发货(),
发货后买家发起退款(),
卖家拒绝了发货后退款(),
买家申诉()
),
TestCase(
买家支付(),
卖家发货(),
发货后买家发起退款(),
卖家同意发货后退款申请()
),
TestCase(
买家支付(),
卖家发货(),
买家超时未处理收货(),
买家完成评价()
)
)
自动生成用例后,我们可以看到一共只需要13条就可以覆盖到整个状态图中的内容了。
这里我们得到的这些都是函数名,完全可以讲每一步骤写成一个自动化脚本,这样会和自动化串起来,自动生成用例,自动执行。