用状态矩阵解决有序操作的case爆炸问题(转载)

转自http://qa.baidu.com/blog/?p=167

作者:qabloger

一、 简介

我们在测试中可能都会面对case爆炸问题。有的case组合是无序的,我们可以通过pict[1]组合case,让pict成最优化的case组合,在保证覆盖率的同时控制case数,这个方式在业界使用广泛,收效也不错。
但是对于有序操作的case爆炸,pict不能支持。目前没有很好的办法,总是让我们很伤脑筋。
本文从状态矩阵出发,讨论了用遍历状态矩阵、先生成所有case、再组合case的方法来解决有序操作的case爆炸问题。

二、 基于ajax的网站测试遇到的问题

1. ajax简介[2]

有序操作的case爆炸问题在java scripts 测试或是应用ajax技术的产品测试中,尤为明显。
AJAX(Asynchronous JavaScript and XML),异步 JavaScript 和XML,不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。
传统的web应用允许用户填写表单(form),当提交表单时就向web服务器发送一个请求。服务器接收并处理传来的表单,然後返回一个新的网页。这个做法浪费了许多带宽,因为在前後两个页面中的大部分HTML代码往往是相同的。由于每次应用的交互都需要向服务器发送请求,应用的响应时间就依赖于服务器的响应时间。这导致了用户界面的响应比本地应用慢得多。
与此不同,AJAX应用可以仅向服务器发送并取回必需的数据,它使用SOAP或其它一些基于 XML的web service接口,并在客户端采用JavaScript处理来自服务器的响应。因为在服务器和浏览器之间交换的数据大量减少,结果我们就能看到响应更快的应用。同时很多的处理工作可以在发出请求的客户端机器上完成,所以Web服务器的处理时间也减少了。

但应用ajax技术也面临着许多问题。无可避免的第一个问题即是浏览器的兼容性问题。各家浏览器对于JavaScript/DOM/CSS的支持总有部分不太相同或是有Bug,甚至同一浏览器的各个版本间对于JavaScript/DOM/CSS的支持也有可能部分不一样。这导致程序员在写 Ajax应用时花大部分的时间在调试浏览器的兼容性而非在应用程序本身。
Ajax技术之主要目的在于局部交换客户端及服务器之间的数据。如同传统之主从架构,无可避免的会有部分的业务逻辑会实现在客户端,或部分在客户端部分在服务器。由于业务逻辑可能分散在客户端及服务器,采用异步交互的方式,而javascript语言本身之能力可能不足以处理复杂的业务逻辑,这导致Ajax应用程序极难维护。

2. 测试中遇到的问题

测试过java script的同学都知道,js有一些功能,单独验证时不会有问题,而将几个功能组合起来时,却很容易出现bug。一些bug可能需要3~4次操作才能够发现。
基于ajax的网站测试中,关于组合操作的测试设计通常依赖测试人员的经验和开发者的设计文档,将可能发生关联的case组合起来。测试执行时也加入一些“随意测试”,即随意组合各种操作,以发现组合操作的问题。这个测试方法在较简单的场景中取得了一定的效果,可以用较小的投入发现问题。但随着网站的ajax技术越来越复杂,更新越来越快,这样做可能存在一些问题:
1. 测试设计的覆盖率不能保证。只凭测试人员的经验,可能不能覆盖到大多数情况,容易漏掉一些分支。
2. 测试执行时的“随意测试”没有依据,测试效果可能因测试人员的不同或测试人员的状态而异。
因此,我们亟待解决的一对矛盾是:
 测试设计的覆盖率。
 测试case要在有限的范围内。

三、 现成的解决方案?

想解决覆盖率和case爆炸的矛盾,目前了解到的解决办法有:

1. pict[1]

 优势:
有一些组合可能是我们不需要进行测试的。通过给组合间加入条件限制,pict可以自动生成需要测试的组合。通过pict的优化和筛选,case组合可以有效的减少。pict工具成熟,应用很广泛,取得了不错的效果。
 不足:
pict所处理的组合之间是无序的,也就是说用pict得到的是一个“组合”而不是“排列”。有时候我们需要处理的是一个操作序列,例如 1-2-3、1-3-2、2-3-1 都是不一样的。而pict对这种顺序关系不能区分。

2. 状态图

 优势:
操作间顺序关系一目了然。
 不足:
无法得出操作序列,仍然只能是看图随机操作。

四、 我们的解决办法

既然状态图可以反映有序操作的关系,我们打算从状态图着手来解决这个问题。

1. 应用场景

我们假设一个常规的电子商务网站。
这个网站中,用户首先进入首页,然后可以搜索产品,进入产品列表页;也可以直接点击首页中的促销产品,进入产品的详细介绍页。
进一步,在产品列表页中,用户可以点击“加入购物车”将商品放入购物车或点击购买直接进入订单页,或对搜索结果不满意而再次搜索;在详细介绍页中,也可以进行如上的操作。
在购物车的页面中,用户可以增、删、改购物车的内容,或继续购物;在订单页中,用户也可以一步步提交订单或继续购物。
我们发现涉及的页面越来越多,其间的关联关系也越来越复杂,大概像下面一张状态图,图中加入了一些“其他功能页”,并假定了他们之间的跳转关系。
用状态矩阵解决有序操作的case爆炸问题(转载)

2. 首先要解决的是覆盖率问题

对这样一个网站,只测试单个页面时,一般问题不大。按照通常的思路来处理,例如搜索产品,订购产品,提交订单,那么问题也不大。可是有时有这样的情况:
 用户习惯先添加到购物车,再统一提交
 提交到一半,发现有的东西不想要了,去掉一两件,重新提交。
 有一部分商品来自购物车,有一部分商品来自新选择的东西。
 已经提交的订单,后台还没处理时,用户有权力修改。

说白了,上面状态图里的各种可能组合,都可以发生,用户可以沿着箭头所指的方向,不停的走下去。如果我们有某种组合没有测到,可能发生一些异常。
为了避免异常的发生,我们要测试尽量多的组合。组合的覆盖率要达到怎样的程度?
根据pairwise[3]的理论,90%以上的bug都是由两两成对的操作导致的。而在网站ajax页面测试中,可能这个数值会稍低一些。依据经验,我们发现大于两步操作的bug还是有一定数量,但是一定要大于三步操作才能发现的bug则很少。
因此我们暂时将覆盖率“拍”为“覆盖所有的三步操作组合”。如果测试设计能够达到这个覆盖率,那应当是非常全面了。
我们先制定这样一个覆盖率目标,事实上这个三步可以理解为N步,只是一个宏定义,再看怎么能简化。

3. 怎样达到这个覆盖率?

目标是“覆盖所有三步操作”,有了上面的状态图,通过遍历状态图,就可以做到了。画状态图,是不是有点麻烦?
进一步,将状态图抽象一下,用状态矩阵来表示,遍历这个状态矩阵,就可以穷尽三步操作。
那首先要完成状态矩阵这个体力活,根据上面的状态图,图中的页面用0-9来表示,从行号到列号,可以转换为1,不可以转换为0。
我们可以得出如下的矩阵:

用状态矩阵解决有序操作的case爆炸问题(转载)

接下来用程序遍历这个矩阵,找出所有2步及3步的操作,可以得到162个组合case:
0 1
0 2
0 3
0 4
0 2 1
0 2 3
0 2 4
0 2 6
0 3 1
0 3 2
0 3 4
0 3 7
0 4 1
0 4 2
0 4 3
0 4 7
2 1
2 3
2 4
2 6
。。。。
有了这些case,覆盖率一定很全了。

4. 如何缩减这些case

覆盖率目标达到了,但是有162个case,除了单个页面的功能验证,我们还要进行这么多的组合case测试,工作量太大。那么如何缩减这些case?
观察这些case,如果我们挨个做,很多步骤是重复的。例如,上面有这样4个case:
0 3 2
3 2 4
2 4 7
4 7 4
这4个case可以首尾相接,变成 0 3 2 4 7 4。把case中有重复的步骤合起来,4个case变成了一个case。
这个思路不错!按照这个思路,我们可以将case合并起来,4合1,case数量大大缩减。
当然,不会那么巧,所有的case都能4合1、3合1,我们还会有些疑问:
1. 是不是所有的case都可以合并? 还是上面的例子只是个特例?
2. 合并到什么程度?是否需要把10多个三步操作都合并起来,得到一个20多步的操作? 这个问题经过讨论,一般认为操作步骤在6~8步比较合理,组合的case不是很大,所能验证的内容也比较多,对于测试人员来讲是最“舒服”的。

这个思路是不是可行,我们先合并一下试试看!

5. 合并后的结果

有了思路就开始做。
先尝试一下把这162个小于3步的操作合并成一些6步的操作。我们采用这样的算法:

1. 先算出所有6步操作,记为集合A; 将所有小于等于3步操作的集合记为B;集合C为最终结果集合,目前是空集;
2. 算出每一个6步操作能够包含几个2-3步的case,并将包含最大值记为max;
3. 如果max>1,则将能够取得max值的那个6步操作,加入集合C;把这个6步操作和其所包含的2-3步操作从各自的集合中删除;返回2继续;
4. 如果max=1,则将集合B中剩余的元素全部加入C中,得到结果集合C,退出

简言之,算法会从所有6步操作的集合中选出能够包含这162个case的最小集合,其中可能有部分case无法合并,无法合并的case直接进入结果集。这个算法可能不是最优的,但是已经尽可能找到了那个最小集。
经过算法合并以后,得出了如下结果:

用多步操作来合成3步操作,需要 35个case,这35个case覆盖了162个case中的151个:
0 2 3 2 3 4
0 3 7 2 4 1
0 4 2 6 2 1
4 7 5 8 4 3
2 6 5 9 5 1
5 2 6 3 7 1
5 3 7 3 7 4
5 4 3 2 6 1
。。。。。。

剩余11个case无法组合:
0 1
0 2 1
0 3 1
0 4 1
8 2 3
8 2 4
8 3 1
8 3 4
8 4 1
8 4 2
9 5 2
这些无法组合的case,是因为有的状态没有出路,走到这里就会终结了。
于是,我们用35个6步的case,覆盖了原来162个case中的151个,剩余11个case,需要再单独验证一下。总case数为35+11=46个,在可以接受的范围内。

我们同样可以尝试用5步操作或7步操作来覆盖2-3步的操作,这里的2-3步也好,6步也好,都可以看作是宏定义,效果相似,case数稍有变化。我们可以尝试几个参数,找到覆盖率和工作量的最佳组合。

6. 人力成本分析

在整个过程中,需要人力投入的工作只有最初的状态矩阵,对于某个网站来说,这个状态矩阵相对稳定,变化不大,后期维护代价也比较小。
将状态矩阵拷贝为txt格式,即可作为程序的输入,执行程序即可得到操作序列。将矩阵的title也读入,直接把序列号替换为title,就可以直接当做case来用了。
有了这样的一组case,我们就可以避免依赖经验的测试或随机测试,又能保证覆盖率了。

五、 总结

如上所述,我们用遍历状态矩阵的方法,首先得到了所有2-3步操作组合。这一个“爆炸”的case集包括162个用例。通过case组合的方式,将162个测试用例变成了35+11=46个测试用例,在保证case覆盖率的同时,缩减case数到一个可测试的范围内。
在维护成本方面,只要维护一个状态矩阵和一个一劳永逸的小算法程序,即可得到测试case与状态图,比较方便。

 注释
[1]. pict: 基于pairwise理论的测试工具,参考 http://www.pairwise.org/tools.asp
[2]. ajax: http://baike.baidu.com/view/1641.htm
[3]. pairwise: http://www.pairwise.org/

http://www.york.ac.uk/depts/maths/tables/orthogonal.htm

上一篇:UART(串口)


下一篇:HAProxy + Keepalived + Flume 构建高性能高可用分布式日志系统