小题大做之【等价类】

小题大做之【等价类】
前言
对于做测试的同学来说,“等价类”用例设计方法可以说是最简单、大家最熟悉,使用最频繁的一种用例设计方法了 。
就等价类和其关联的一些内容,近来抽空把平时碰到的一些问题总结了一下 , 细思之后,其实这个方法也是有一定的复杂性和微妙之处的 , 当然也非常有意思,这里分享一下,欢迎大家指正和补充 。
1. 等价类方法简介
对等价类这个用例设计方法 , 通用解释大概整理归纳为以下几点:
等价类划分方法使用前提: 设计输入规则:
【小题大做之【等价类】】举例说明:
2. 追问几个为什么?
在划分等价类的时候,下意识会按我们的常识进行分类,如:
那么,这么划分的依据是什么?
3. 思考一:到底什么是等价?
见字知意,先从这个名字分析入手 。“价”是价值,“等”是相同,简单看就是如果多个输入值具有相同的价值,则这些值就算等价了 。那么价值是相对于谁来讲的呢?是对于用户的价值,还是对于测试人员的价值?这里认为应该是对于被测系统的价值!
对于黑盒测试,被测的功能可能是一个函数、一个接口或者是一串方法的链路 。如果两个输入的数据 , 在被测系统中拥有相同的处理逻辑,那么这两个输入的数据就是等价的,测试的时候只选取任意一个即可 。
针对等价类划分的依据,我们试着打破黑白盒的限制,举几个简化的例子说明一下 。
3.1. 显而易见的等价类划分
如图:
如果输入100、9、28这些值,程序都输出Y,同样这些值也属于同一个等价类 。综上,根据程序处理逻辑,划分为两个等价类:
那能否将输出是否相同当做等价类划分的依据呢?
3.2. 不要用输出做等价类划分的依据
如图:
如果输入0、1、-9、100 这些值,程序都输出N,但它们能都划分到一个等价类里面么?显然是不可以的 。由图可见,x=1的时候,走的是最左侧分支 , 测试的是最左侧分支上的代码 。而当x=100的时候,走的是最右侧分支,测试的是最右侧分支上的代码,二者的处理逻辑明显不同 。
因此针对该被测程序 , 应该划分为3个等价类:
所以,仅看程序的输出是否相同,不能作为等价类划分的充分依据 。
3.3. 分析处理逻辑和路径
如图:
看以上这个程序,针对输入Q又如何划分等价类呢?我们借鉴白盒测试的思路试试 , 该程序一共有6个被测语句,其中2个判断语句(1、4)和4个赋值语句(2、3、5、6) 。
上面设计的两个输入 , 分别将1 - 6的每个语句都测试到了,是否就意味这是一个充分的测试呢?显然还不是 , 接下来再设计两个输入,考虑语句之间的组合测试:
以上四个输入的设计,也可以看做是遍历程序路径,如下图红色线条所示 , 明显测试覆盖程度更加充分:
Q=2 【上左 -> 下右】
Q=16 【上右 -> 下左】
Q=5 【上左 -> 下左】
Q=9 【上右 -> 下右】
等价类1: Q < 5
等价类2: Q >= 11
等价类3: Q=5
等价类4: 5 < Q < 11
以上设计实际引入了白盒单元测试中“路径覆盖”的思路 , 不但遍历每条语句,还要考虑语句之间的组合关系 , 从而做到最充分的测试覆盖 。当然这种覆盖方式成本极高,尤其是程序逻辑复杂的时候,因此公司内一般会要求达到“语句覆盖”或者“分支覆盖”即可的程度,这种覆盖率的统计也是靠类似单元测试工具自动分析得出的,从而作为开发阶段的输出准则或者测试阶段的进入准则,此处不赘述了 。
回过头再看“等价”,判断多个输入数据是否等价的依据,是这些输入在系统中的逻辑处理是否一致,如果一致则认为它们是等价的,属于一个输入集合,测试的时候从中选取一个代表性的输入即可 。
任何方法的使用不可绝对化,需要结合人员经验、时间、复杂度、优先级等因素综合考量,选择适当的测试粒度,过犹不及 。在深挖测试方案的时候,如果得不到明确的处理逻辑,可以尽量和开发人员沟通问询 , 帮助我们判断等价类的划分标准,争取使我们的测试设计更加精炼准确 。
4. 思考二:到底有多少个等价类?
假设某系统前端页面中要求用户输入自己的年龄,典型的等价类用例设计应用场景 。
好吧,用前面分析的方案,这些所有的无效输入,其实内部处理没有区别,因此它们属于一个大的等价类,我们任选一个就行了 。
这么干好像也没啥错哈,但是我们再纠结一下下,这些牛鬼蛇神的分类方法到底是从哪里来的,能不能捋一捋,溯溯源 。
我们在系统中所有能输入的字符都集合一个地方中 —— 字符集 。就从这儿简单说起吧 。
4.1. 关于字母类别的划分4.1.1. ASCII字符编码
计算机出现后,第一个可用于输入的字符集合——ASCII码 。里面都有什么呢?
ASCII码中包括大小写英文字母(52个)、阿拉伯数字(10个)、标点及运算符号(33个)、控制字符(33个)等共128个字符,分别用7位二进制数对其进行编码,但一个ASCII码的存储占一个字节(8位,最高位统一为0) 。
在这里面我们找到了字母(大小写)、数字和特殊符号的来源,也是平常测试中最常见的输入来源,或者说这是部分等价类划分的依据 。
4.1.2. 系列字符编码
是一系列字符编码的集合,目前包括了15个字符集 , 主要针对欧洲国家的语言进行编码,标准编号分别为 -1 至 -15,其中-1收录了西欧语言,是最常用的一种字符集 。其它标准分别针对如东欧语言、中欧语言、希腊语、斯拉夫语等等 。
这些字符集都以单字节编码,即每个字符用一个字节(8位)来编码和存储,向下兼容ASCII码,即在ASCII码(128个字符)的基础上进行了扩展(又扩展了128个字符) 。
说到这里,我们在做测试的时候,可以输入的字符类型就多起来了,西欧、东欧、南欧、北欧、斯拉夫 。。。,头大了,别着急,一般情况下系统使用了统一的字符集形式,即便输入这些字符也不会导致系统出问题的 。但是如果系统涉及到与外部数据的交互,则需要考虑对方传来的数据使用了何种编码方式 , 要进行相应的转码才可以,否则就会出现乱码 。
4.2. 关于汉字类别的划分4.2.1.字符编码
该字符编码标准由中国国家标准总局于1980年发布,适用于汉字处理,共收6763个汉字和682个非汉字图形字符(全角字符),这些字符均使用2个字节来存储 。
注意:为了排版的对齐和美观,对字母、符号又设计了“全角”字符,这些字符相对于ASCII码里面的字母和符号,全都变胖了一倍,即用2个字节表示一个字符 。而原ASCII码里面的字母和符号我们可以对应称之为“半角”字符 。其实全角字母等同于汉字,是我们特有的“中文字母” 。
那么在测试的时候,为了严谨,针对“字母”的等价类输入,我们可以规范写成“半角字母”或“英文字母”,尽量减少不必要的歧义产生 。
曾经见到过某些系统,注册时输入用户名,通过前端脚本判断,自动将用户输入的“全角字符”转成了“半角字符”,而没有直接给用户弹出“请输入英文字母”的提示,提升了系统的用户感官和易用性 。
4.2.2. 其它中文编码 4.2.3.和 UTF8
前面提及的各类字符集基本都属于各自不同的国家或地区,编码的相互兼容性是令人头疼的问题,因此后来出现了字符集,它定义了世界上大部分字符的编码方式,包括字母、数字、符号、汉字等,基本上可以做到一套编码世界通用 。随着的出现,逐渐取代了其它各类的字符集编码 。
UTF是一种字符集的编码方式,简单理解就是中规定了字符的集合,UTF负责把这些字符转成不同格式的二进制编码 , 使计算机可以识别 。常见的编码方式有UTF8、UTF16和UTF32,其中最常用的是UTF8(或写成UTF-8) 。
UTF-8想必大家都不陌生了,无论是前端编码、后端开发还是数据库设计都离不开它,几乎已经成了开发过程中的一个事实上通用的规范标准了 。
在测试过程中,我们所有能够输入的字符,在一定程度上说 , 都来自于UTF-8这个字符集 , 那么如果对输入字符进行分类,其就可以作为参考依据了 。
另外,UTF-8是一种变长的编码方式,一个字符在存储的时候可能会占用1到4个字节不等,像MySQL定义字段类型 (10) 时,表示最多可以存储10个字符,而不是10个字节 。如果对存储空间敏感,测试的时候就要注意了,需要分析存储的字符类型及预估存储空间 。大多情况下,UTF-8存储英文字母(ASCII码中)占用1个字节,存储扩展字母(扩展区域)时占用2个字节,存储常见汉字占用3个字节 。
4.3. 小数和负数的输入问题4.3.1. 先看输入
在进行等价类划分的时候,经常看到有人对数字类型的输入细分为:整数、小数、负数等多个集合,我们再来分析一下在Web环境下这么分类所存在的问题 。
在Web环境下 , 输入的测试一般通过浏览器展示的前端页面来完成 , 如登录页面、表单页面、查询页面等 。而前端浏览器页面展示依靠的是HTML语言,这个语言中并没有数据类型的划分,或者说页面见到的一切文字和输入的字符,其本质都是一段字符串 。
我们来看百度首页,查看HTML源码可知,该搜索框是一个“输入”类型的元素控件,其输入的内容都属于“文本(text)”类型,也就是说,不论输入3、3.8、-9 等任何这些数字,其本质都是文本字符:
如果在前端输入类型元素中 , 输入字符135,是怎么编码及传输到后端的呢?
4.3.2. 再看输出
在一定程度上看,用户的输入最终都保存到数据库中了 , 可以看作是程序的输出 。我们知道数据库中的字段是有类型限定的,常见的有日期、文本、整型、浮点等等 。接下来我们以MySQL数据库中整型进行分析 。
MySQL存储整型数据有四种类型:
如果我们使用int类型,存储数字135,是怎么分配空间的呢?
4.3.3. 综合起来看
同样都是处理135 , 可以看到前端和数据库端明显不一样 , 前端是文本text类型 , 数据库端是整型int类型,说明中间的程序处理 , 必须要进行一次“类型转换”,将文本类型的135转为整型的135才可以 。
回到等价类划分 。假设测试一个年龄的输入,此时测试逻辑就抽象为“将输入的字符转为正整型数据存储在数据库中”,在不考虑边界情况下:
另外做个补充 。如果是浮点类型,除了考虑以上各种情况 , 还要注意构造输入数据时小数点的个数,前端如果只限定了输入的数据只能是“数字”和“小数点”两种字符类型,没有校验小数点的个数,那么像“2.3.6”这样的数据,符合前端的限定,但在后端进行类型转换的时候一样也会报错,当然后端接口上对输入的参数数据也要先进行校验 。
4.4. 关于空格问题
在进行测试的时候,“空格”往往是个恼人的因素,到底是有效呢还是无效呢?
在输入中存在空格一般有三种情况,前面有空格、后面后空格、中间有空格 。而且主要影响前端的显示效果,给用户造成无端的困扰 。
前端页面HTML对于空格的显示和其他字符不一样,默认情况下,你的数据中不管有多少个空格,都显示为1个空格,即便在数据库中存在的空格数量是正确的,但是在页面显示效果上只可见1个空格 。在用户看来就是3条重复的数据 。
对于空格的处理应该纳入开发规范,明确说明在使用数据前必须先自动去除首尾空格 , 不允许中间使用空格等 。
当然如果业务确实需要使用空格,那么前端页面必须对空格进行额外的处理,如预格式化文本,使用实体的方式表现空格等等 。
综上,在测试时可以将空格拿出来进行统筹的考虑,而不用纠结置于等价类的那个等价集合中 。
5. 思考三:等价类还能干点儿啥?
前面的介绍我们基本都在关注于前端页面的输入部分 , 尤其是通过键盘可以敲入的字符输入 。等价类其实是一种测试的思想,并不仅局限于在前端页面中进行字符输入的测试 。
1). 下拉列表
下拉列表是页面中常见的元素控件,列表项有固定写死的,也有从数据库动态读取的 。如果一个列表中有多个列表项,怎么设计测试策略呢?
借鉴等价类思想,把列表项按不同的处理逻辑进行分类,每个分类选取一个进行测试 。这里的等价类往往有个特点,就是全都是有效等价类 , 而没有无效等价类 。
2). 地址栏构造无效输入
还是用百度首页举例,怎么给那个经典的搜索框设计一个无效字符的输入呢?好像你能输入进去的都能给你找到搜索结果 。
观察一下 , 输入“北京”点击搜索后,输入的字符“北京”出现在了地址栏中(典型的Get方式提交表单),把这个地址栏中的内容都拷贝出来,粘贴到一个记事本中,发现“北京”二字又变成了一段十六进制编码(以百分号分隔),用前面的知识分析,这是“北京”二字的UTF-8编码 。
到这里就好办了 。在搜索框中输入字符和在地址栏中输入字符的UTF-8编码,二者的提交效果是一致的,类似图中的办法,我们构造一个无效的UTF-8编码,然后粘贴回地址栏上回车,百度的后端服务器会判断这里有一个无效的输入字符 。
3). 接口测试
接口测试其本质也属于黑盒测试 。测试的时候一般也不关注接口的实现代码,而是通过构造入参检查接口的输出 , 是不是像黑盒测试,只是测试的时候需要借助类似等专门的工具,而不是手工去完成 。其实也好理解,黑盒测试、白盒测试是按测试的类型划分的 , 单元测试、接口测试、系统测试是按测试的阶段和对象划分的,他们之间不是互斥的 。就像男人、女人的划分与青年、老年的划分一样不冲突 。
接口需要对入参进行校验 。测试的时候我们就需要对接口的入参分别设计各类情况:
4). 其它等价类方法的应用场景
6. 尽可能再提高测试效率
再次强调 , 等价类是一种思想,作为测试的同学必须要掌握 。但是能不能把它更加简化和高效呢?就像不懂机械原理也能开好车,但是懂得机械原理开车时会更有自信和得心应手,嗯,这是个关于开车的问题 。
以下参考权当抛砖引玉了 。
6.1. 检查正则表达式 PK 页面一遍遍的输入
利用正则表达式对输入的数据进行类型、格式等校验 。此时完全可以借助现有的正则工具对所有构造的数据进行一次性快速校验 。
6.2. 检查注解 PK 页面一遍遍的输入
例如在Java开发体系中,有很多现有的框架提供注解的方式进行参数校验 。我们可以通过走查的方式直接检查注解的使用,而没有必要手工在页面去一遍遍的输入试错 。
此时像一些“类型检查”、“非空检查”、“数值区间检查”、“边界检查”、“日期检查”、“正则检查”、“Email检查”等等都可以交给现成的注解来完成 。
如果通过一个类似检查表的东西是不是就可以大大提高测试的效率 , 不用再去有效等价类、无效等价类的去设计输入,也不用耗时间去执行测试用例了 。