高效测试用例组织算法pairwise之Python实现

发表于:2018-2-13 10:20

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:kuzaman    来源:博客园

  开篇:
  测试过程中,对于多参数参数多值的情况进行测试用例组织,之前一直使用【正交分析法】进行用例组织,说白了就是把每个参数的所有值分别和其他参数的值做一个全量组合,用Python脚本实现,就是itertools模块中product方法(又称笛卡尔积法)。
  正交分析法的优点是测试用例覆盖率100%,缺点测试用例数量庞大,执行用例消耗的人工巨大。
  Pairwise (结对)算法源于对传统的正交分析方法优化后得到的产物,它的理论来自于数学统计。毫不避讳的说,本人看不懂数学统计中的学术论文,只能从网上找一些通俗简单的说法来理解其基本含义。
  网上很多人都实例都是用  【操作系统浏览器,语言环境】来举例的,本人也做同样示例:
  操作系统: W(Windows),L(Linux),Mac (Mac) ;浏览器:M(Firefox),O(Opera),IE;语言环境:C(中文),E(英文)
  按照正交分析法:会产生3x3x2=18种组合方式 ,测试用例覆盖率100%。
  Pairwise结对测试用例组织法,可压缩到9种组合方式。因此优点是测试用例数量少,缺点是一定会有漏测。
  引论:
  一、Pairwise算法的核心理念
  1、一组测试用例(每个用例有3个参数的值组成,如[W,M,C])中每一个2个元素组合起来,两两组合,就有3种组合方式(有位置的[W,M][W,C][M,C]);
  2、这第一组测试用两两组合出的3种组合方式,与其他组元素的对比原则是 :[W,M]只会和其他组的第一个元素对比,[W,C]只会和其他组中第二个元素对比。。。。;
  如果 [W,M][W,C][M,C]这三个元素分别出现在其余有效组相同位置的元素中,就可以认为这一组Case为多余Case,并进行删除。
  名词解释:【有效组】表示未被删除的组和未被对比过的组。举例:第1,3组被删除,则第4组要对比的有效组为第2,5,6,7...18组。有效组这里踩过坑%>_<%
  3、最终得到测试用例,就是结对算法计算出来的最优测试用例集合。
  二、牛逼闪闪的学术证明
  Pairwise是L. L. Thurstone(29 May1887 – 30 September 1955)在1927年首先提出来的。他是美国的一位心理统计学家。Pairwise也正是基于数学统计和对传统的正交分析法进行优化后得到的产物。
  Pairwise基于如下2个假设:
  (1)每一个维度都是正交的,即每一个维度互相都没有交集。
  (2)根据数学统计分析,73%的缺陷(单因子是35%,双因子是38%)是由单因子或2个因子相互作用产生的。19%的缺陷是由3个因子相互作用产生的。
  因此,pairwise基于覆盖所有2因子的交互作用产生的用例集合性价比最高而产生的。
  正文
  一、思路
  对一个测试场景如何从何从输入被测条件,到产出Pairwise测试用例,使用Python编程思路如下:
  1、将allparams=[['M','O','P'],['W','L','I'],['C','E']]进行笛卡尔积全组合处理,生成正则分析法产生的全量测试用例集合的一维数组(len=N);
  2、将全量测试用例中的每个测试用例,都进行两两组合的分解处理,生成与全量测试用例集合 长度相同的二维数组(一维 len=N);
  3、使用Python版Pairwise算法剔除无效测试用例,最终得到有效的结对测试用例集合;
  代码第1,2函数利用Python自带数学计算库itertools编写,代码第3函数为本人死磕出来的代码。
  二、直接上代码
  1 # -*- coding: utf-8 -*-
  2 from datetime import *
  3 import random,os,copy,time
  4 import logging
  5 import itertools
  6 '''
  7 #Author:Kuzaman
  8 #Time:2017-07-18
  9 '''
  10 class utils2 :
  11     #1、笛卡尔积 对参数分组全排列
  12     def product(self,allparams):
  13         newlist=[]
  14         for x in eval('itertools.product'+str(tuple(allparams))):
  15             newlist.append(x)
  16         return newlist
  17
  18     #2、对笛卡尔积处理后的二维原始数据进行N配对处理,得到Pairwise计算之前的数据
  19     def get_pairslist(self,productedlist,pairlen):
  20         pwlist = []
  21         for i in productedlist:
  22             subtemplist = []
  23             for sublista in itertools.combinations(i,pairlen):
  24                 subtemplist.append(sublista)
  25             pwlist.append(subtemplist)
  26         return pwlist
  27
  28     #3、进行Pirwise算法计算
  29     def pairwise(self,allparams,pairlen):
  30         productedlist=self.product(allparams)   #productedlist笛卡尔积全排列组合的测试用例
  31         self.pprint(productedlist)
  32         print ('笛卡尔积全排列组合数量:',len(productedlist),'--'*11)
  33         listb = self.get_pairslist(productedlist,pairlen)   #listb:对测试用例结对拆分后的二维列表
  34         sublistlen = len(listb[1])  #sublistlen:每个测试用例拆分后一维列表长度
  35         flag = [0]*sublistlen       #一条测试用例拆分后,每个结对在二维列表同位置上是否有相
  36                                     #同值,其标识列表为flag,flag长度根据sublistlen改变
  37         templistb = copy.deepcopy(listb)#【有效组】的原始值与listb相同
  38         delmenu = []    #无效测试用例编号列表
  39         holdmenu=[]     #有效测试用例编号列表
  40         # self.pprint (listb)
  41         print ('--'*7,'有效测试用例计算结果','--'*7)
  42         for cow in listb:           #一维遍历,等同于二维数组按照行遍历
  43             for column in cow:      #二维遍历,等同二维数组中任意一行按照‘列’横向遍历
  44                 for templistbcow in templistb:      #【有效组=templistb】中按照行,从上至下遍历
  45                     Xa = cow.index(column)          #待校验元素的横坐标
  46                     Ya = listb.index(cow)           #待校验元素的纵坐标
  47                     #有效组中行不能是当前要对比元素所在的行,
  48                     #且带对比元素与【有效组】的行templistbcow中的坐标Xa元素相同,
  49                     #则认为对比成功,跳出第三层for循环。
  50                     if templistbcow != cow and column == templistbcow[Xa]:
  51                         # print (column,'===>' ,templistbcow[Xa],'相等了。。。')
  52                         flag[Xa] = 1   #1表示对比成功
  53                         break
  54                     else: #对比不成功,需要继续第三层for循环对比
  55                         # print (column,'===>' ,templistbcow[Xa],'不不不等了。。。')
  56                         flag[Xa] = 0   #0表示对比不成功
  57             # print ('下标%d,子元素 %s 双匹配对比结果flag:%s'%(listb.index(cow),cow,flag))
  58             if 0 not in flag:    #如果对比列表中都是1,表明该行的所有结对都在同列的对应位置找到了
  59                 num = listb.index(cow)
  60                 delmenu.append(num)     #记录该无用用例所在总列表listb中的位置
  61                 templistb.remove(cow)   #有效组中删除该无用用例,保持有效性
  62                 # print ('下标为%d行应删除,内容=%s,'%(num,cow))
  63                 # print ('delmenu:',delmenu)
  64             else:  #如果有0出现在对比列表flag中,则表示该行所有结对组并未全在同列对应位置找到,此用例为有效用例应保留
  65                 num2 = listb.index(cow)
  66                 holdmenu.append(num2)     #记录有效用例在总列表listb中的坐标
  67                 # print ('---------------下标为%d行应保留,内容=%s'%(num2,cow))
  68                 # print('holdmenu=',holdmenu)
  69             # print ('***'*20)
  70         print ('保留元素列表:%s \n匹配重复元素列表:%s'%(holdmenu,delmenu))
  71         return self.pwresult(productedlist,holdmenu)
  72
  73     def pwresult(self,slist,holdmenu):
  74         holdparamslist = []
  75         for  item in holdmenu:
  76             holdparamslist.append(slist[item])
  77         return holdparamslist
  78
  79     def pprint(self,list):
  80         for item in list:
  81             print ('line %d:'%(list.index(item)+1),item)
  82             # print (item)
  83
  84 if __name__ == '__main__':
  85     u2 = utils2()
  86     # allparams=[['M','O','P'],['W','L','I'],['C','E'
  87     allparams=[['M','O','T'],['L','I','T'],['s','T','E','K'],[1,3],['Yes','No'],['666','']]
  88     finallist = u2.pairwise(allparams,4)
  89     print('最终保留测试用例个数:%d 个'%(len(finallist)))
  90     u2.pprint(finallist)

  代码解读:
  第三for循环代码39~48行,主要是垂直判断 待检测元素 与 相同位置的元素是否有相同的
  第二for循环代码38~48行,把一组测试用例中的两两配对,从左至右分别和同位置的元素作对比
  第一for循环代码37~48行,遍历每一组测试用例。
  第50~58行代码,判断一组用例的两两配对在其他组同位置上从上到下都能找到相同元素,则将改无效Case从templistb中删除,保持templistb的有效性。

  执行结果:
  line 1: ('M', 'W', 'C')
  line 2: ('M', 'W', 'E')
  line 3: ('M', 'L', 'C')
  line 4: ('M', 'L', 'E')
  line 5: ('M', 'I', 'C')
  line 6: ('M', 'I', 'E')
  line 7: ('O', 'W', 'C')
  line 8: ('O', 'W', 'E')
  line 9: ('O', 'L', 'C')
  line 10: ('O', 'L', 'E')
  line 11: ('O', 'I', 'C')
  line 12: ('O', 'I', 'E')
  line 13: ('P', 'W', 'C')
  line 14: ('P', 'W', 'E')
  line 15: ('P', 'L', 'C')
  line 16: ('P', 'L', 'E')
  line 17: ('P', 'I', 'C')
  line 18: ('P', 'I', 'E')
  笛卡尔积全排列组合数量: 18 ----------------------
  -------------- 有效测试用例计算结果 --------------
  保留元素列表:[1, 3, 4, 7, 9, 10, 12, 14, 17]
  匹配重复元素列表:[0, 2, 5, 6, 8, 11, 13, 15, 16]
  最终保留测试用例个数:9 个
  line 1: ('M', 'W', 'E')
  line 2: ('M', 'L', 'E')
  line 3: ('M', 'I', 'C')
  line 4: ('O', 'W', 'E')
  line 5: ('O', 'L', 'E')
  line 6: ('O', 'I', 'C')
  line 7: ('P', 'W', 'C')
  line 8: ('P', 'L', 'C')
  line 9: ('P', 'I', 'E')
  [Finished in 0.6s]
  如果我们要测试的参数组合是这样子的:
  allparams=[['M','O','P'],['W','L','I'],['C','E','K'],[1,2,3],['Yes','No'],['666','']]
  全排列组合会生成324种组合,按照 2元素结对的pairwise算法会缩减到41个有效组合;
  按照 3元素结对的pairwise算法会缩减到88个有效测试用例;
  按照 4元素结对的pairwise算法会缩减到114个有效测试用例。
  ↑这里整数可在代码91行进行调整
  三、代码核心内容白话解释
  pairwise(self,allparams,pairlen)函数包含3层for循环,先画一个二维数组:
  i[0]        i[1]        i[2]
  listb.index(i)=0 : [('M', 'W'), ('M', 'C'), ('W', 'C')]
  listb.index(i)=1 : [('M', 'W'), ('M', 'E'), ('W', 'E')]
  listb.index(i)   : [('M', 'L'), ('M', 'C'), ('L', 'C')]
  listb.index(i)   : [('M', 'L'), ('M', 'E'), ('L', 'E')]
  listb.index(i)   : [('M', 'I'), ('M', 'C'), ('I', 'C')]
  listb.index(i)   : [('M', 'I'), ('M', 'E'), ('I', 'E')]
  listb.index(i)   : [('O', 'W'), ('O', 'E'), ('W', 'E')]
  listb.index(i)   : [('O', 'L'), ('O', 'C'), ('L', 'C')]
  listb.index(i)   : [('O', 'L'), ('O', 'E'), ('L', 'E')]
  listb.index(i)   : [('O', 'I'), ('O', 'C'), ('I', 'C')]
  listb.index(i)=n : [('O', 'I'), ('O', 'E'), ('I', 'E')]

  二维列表,其中的行(发音:hang,二声。横着的那排)从上到下就是第一层for循环 ,每一行中的i[0],i[1],i[2]就是第二层for循环从左至右,第三次for循环元素i[x]从上之下与有效组 templistb通位置元素的对比。
  1、第n行的i[0]要和有效templistb的其他行的i[0]元素对比(第三for),如果有相等的,记录一个标识 如 flag1=True,如果没有相等的记录falg1=False;
  2、直到第二for中的i[0],i[1],i[2]都进行对比后,会得到  [flag1,flag2,flag3 ],所有flag=True则该行为无效用例
  3、第一for遍历全部组合,最终得到保留下来的有效templistb
  见图:
  完结篇
  以上是自己编写的pairwise的全部内容,此算法共耗时3天:
  第一天在确定这究竟是什么算法,看了很多学术文献,看不懂;
  第二天开始写程序,for的嵌套循环设计耽误很久;
  第三天程序成型,有执行结果,发现与参考文章结论不同,随后再仔细研读参考文章,发现掉坑里了。重新推翻代码按照正确思路,用2个小时完成最终结果。
  本人做测试的,还不是专业的测试开发,写代码比较费劲,真正应了设计占70%,编码占30%的理。
  希望对需要组织测试用例,或者自动化测试中需要组织用例的同行们有所帮助。

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号