1.等价类测试
等价类测试是把所有可能的输入数据,即程序的输入域划分为若干个等价类,然后从每一个子集中选取少数具有代表性的数据作为测试用例。该方法是一种重要的,常用的黑盒测试用例设计方法。
简单来说,使用等价类测试的目的就是为了在测试资源有限的情况下,用少量有代表性的数据得到比较好的测试效果!
1.等价类的划分
等价类的重要问题是它们构成集合的划分!划分是指互不相交的一组子集,这些子集的并是整个集合。
划分可定义为:给定集合 B B B,以及 B B B的一组子集 A 1 , A 2 , ⋯ , A n A_{1},A_{2},\cdots ,A_{n} A1,A2,⋯,An,这些子集是B的一个划分,当且仅当 A 1 ∪ A 2 ∪ ⋯ ∪ A n = B A_{1}\cup A_{2}\cup \cdots \cup A_{n}=B A1∪A2∪⋯∪An=B,且 i ≠ j i\neq j i=j时, A i ∩ A j = ∅ A_{i}\cap A{j}=\varnothing Ai∩Aj=∅。
等价类划分是将输入定义域进行一个划分,并且划分的各个子集是由等价关系决定的。 测试某等价类的代表值就等于对这个类中的其他值的测试。也就是说,如果等价类中某个输入条件不能导致问题发生,那么该等价类中其他的输入条件进行测试也不能发现错误!
等价类划分有两种不同的情况:有效等价类和无效等价类。
有效等价类是指对于程序的规格说明来说是合理的,有意义的输入数据构成的集合。
无效等价类与有效等价类的定义恰好相反。无效等价类是指对于程序的规格来说是不合理的或无意义的输入数据所构成的集合。对于具体的问题,无效等价类至少应有一个。
**在设计测试时,要同时考虑这两种等价类。因为用户在使用软件的功能时,有意或无意输入一些非法的数据是常见的事情。**软件不仅要能经受意外的考验,这样的测试才能保证软件具有更高的可靠性!
2.划分等价类的方法
等价类测试的思想就是吧全部输入数据合理划分为若干个等价类,在每一个等价类中取一个具有代表性的数据作为测试的输入条件,这样就可以用少量的测试数据取得较好的测试效果!
在等价类测试中,划分等价类非常关键。如果等价类划分合理,可以大大减少测试用例,并能保证达到要求的测试覆盖。一般按照以下的规则进行划分!
(1)按照区间划分:在输入条件规定了取值范围的个数的情况下,则确定一个有效等价类和两个无效等价类。例如,程序的输入是学生成绩score,其范围为0~100。
有效等价类为: 0 ⩽ s c o r e ≤ 100 0\leqslant score\leq 100 0⩽score≤100;
无效等价类为: s c o r e < 0 , s c o r e > 100 score<0,score>100 score<0,score>100
(2)按照数值划分:在规定了输入数据的一组值(假设有n个),并且程序要对每一个输入值分别处理的情况下,可确立n个有效等价类和一个无效等价类。
例如:程序输入x取值与一个固定的枚举类型{1,2,4,12},并且程序中对4个数值分别进行了处理,则有效等价类为x=1,x=2,x=4,x=12,无效等价类为1,2,4,12以外的值构成的集合。
(3)按照数值集合划分:在输入条件规定了输入值的集合或者规定了“必须如何”的情况下,可确立一个有效等价类和一个无效等价类。例如,某程序中有规定标识符,其输入 条件规定“标识符必须以字母开头……”。
则可以这样划分等价类:“以字母开头者”作为一个有效等价类,“以非字母开头者”作为无效等价类。
3.用等价类设计测试用例
1.划分等价类
首先根据输入条件或输出条件划分等价类。
2.建立等价类表
输入 | 有效等价类 | 无效等价类 |
---|---|---|
3.选择测试用例
从等价类中选取具有代表性的数据设计测试用例。从等价类中选择测试用例时,一般遵循以下原则。
(1)为每一个等价类规定一个唯一的编号。
(2)设计一个测试用例,使尽可能多地覆盖尚未被覆盖的有效等价类。重复这一步,直到所有的有效等价类都被覆盖!
(3)设计一个测试用例,使其仅覆盖一个尚未被覆盖的无效等价类,重复这一步,直到所有的无效等价类都被覆盖为止。
2.基于判定表测试
在一些数据处理问题中,某些操作是否实施依赖于多个逻辑条件的取值。在这些逻辑条件取值的组合所构成的多种情况下,分别执行不同的操作。处理这类问题的一个非常有力的分析和表达工具是判定表,或称为决策表。判定表能够将复杂的问题按照各种可能的情况全部列举出来,简明并避免遗漏。
1.判定表的组成
判定表通常有四个部分组成,如下表所示:
桩 | 规则 |
---|---|
条件桩 | 条件项 |
动作桩 | 动作项 |
(1)条件桩:列出了问题的所有条件。
(2)动作桩:列出了问题规定可能采取的操作。
(3)条件项:列出针对它所列条件的取值。
(4)动作项:列出了条件项的各种情况下应该采取的动作。
动作项和条件项紧密相关,它指出了在条件项的各组取值情况下应采取的动作。任何一个条件组合的特定取值及其相应要执行的操作称为规则。
显然,判定表中列出多少条件组合取值,也就有多少条规则。
在判定表中国贯穿条件项和动作项的一列就是一条规则。
桩 | 规则1 | 规则2 | 规则3 | 规则4 |
---|---|---|---|---|
条件1 | T | T | F | F |
条件2 | T | F | T | F |
动作1 | √ | √ | ||
动作2 | √ | √ |
2.考生录取的案例
某程序规定:对总成绩大于450分,且各科成绩均高于85分或者是优秀毕业生,应优先录取,其余情况作其他处理。请建立判定表。
解题:
1)列出所有的条件桩和动作桩
条件桩:
(1)总成绩大于450分吗?
(2)各科成绩均高于85分吗?
(3)是优秀毕业生吗?
动作桩:
(1)优先录取
(2)作其他处理
2)确定规则的个数
本例中输入有三个条件,每个条件的取值为“是”或“否,因此有 2 3 = 8 2^{3}=8 23=8中规则”。
3)填入条件项
在填写条件项时,可以将各个条件取值的集合进行笛卡尔积,得到每一列条件项的取值。本例就是计算{Y,N}{Y,N}{Y,N}的笛卡尔积,然后将所得集合中的每一个元素的值填入每一列条件项中,如下表所示:
4)填入动作桩和动作项
根据每一列中各条件的取值得到所要采取的行动,填入动作桩和动作项,便得到初始判定表。如下所示:
5)化简
如果某个条件项的取值变化对动作桩的取值没有影响,则可以考虑合并规则,具体简化后的判定表如下所示:
3.综合题解析
用决策表测试法测试以下程序:该程序有三个输入变量month、day、year(month 、 day和year均为整数值,并且满足:1≤month≤12和1≤day≤31),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期(next day)。例如,输入为 2004 年11月29日,则该程序的输出为2004年12月1日。
(1) 分析各种输入情况,列出为输入变量 month 、 day 、 year 划分的有效等价类。
(2) 分析程序的规格说明,并结合以上等价类划分的情况,给出问题规定的可能采取的操作(即列出所有的动作桩)。
(3) 根据 (1) 和 (2) ,画出简化后的决策表。
(4) 开发一个能够生成合法年月日的测试用例自动生成的小工具。
解题步骤如下所示:
(1).分析各种输入情况,可以分别对变量year,day以及month进行等价类的划分,具体如下所示:
month变量的有效等价类:
M1={month=4,6,9,11}
M2={month=1,3,5,7,8,10}
M3={month=12}
M4={month=2}
day变量的有效等价类
D1={1≤day≤26}
D2={day=27}
D3={day=28}
D4={day=29}
D5={day=30}
D6={day=31}
year的有效等价类
Y1={year是闰年}
Y2={year是平年}
(2)分析程序规格说明,结合以上等价类划分的情况给出的问题规定的可能采取的操作(即列出所有的动作桩)。考虑各种有效的输入情况,程序中可能采取的操作有以下六种
a1:day+2;
a2:day=1
a3:day=2
a4:month+1
a5:month=1
a6:year=1
a7:不可能
(3)简化后的决策表如下表所示
如果不简化可能会有246=48种,数据量太大,因此这里仅仅给出简化后的决策表,具体如下所示:
(4)使用Java语言开发的小工具如下所示,这里该工具首先是随机生成一个合法的日期,然后计算该日期隔一天后的日期,具体代码如下所示:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class MyDate {
private int year;
private int month;
private int day;
//随机生成合法日期
public static MyDate randomDate(){
MyDate date=new MyDate();
Random random=new Random();
//随机生成符合规范的年,并判断是否为闰年
int year = random.nextInt(51) + 2000;//随机生成2000-2050之间的年份
int month = random.nextInt(12) + 1;//随机生成1-12的月份
//定义两个集合
List<Integer> list1=new ArrayList<>();
Collections.addAll(list1,1,3,5,7,8,10,12);//list1中的集合元素有31天
List<Integer> list2=new ArrayList<>();
Collections.addAll(list2,4,6,9,11);//list2中的集合元素有30天
int day;
if(list1.contains(month)){
//表示该月有31天
day=random.nextInt(31)+1;
}else if(list2.contains(month)){
//表示该月有30天
day=random.nextInt(30)+1;
}else{
//表示该月为2月
if(isLeafYear(year)){
//如果当年为闰年
day=random.nextInt(29)+1;
}else{
day=random.nextInt(28)+1;
}
}
date.year=year;
date.month=month;
date.day=day;
return date;
}
//计算隔一天后的日期
public static MyDate computerDate(MyDate date){
MyDate tomorrow =new MyDate();
tomorrow.year=date.year;
tomorrow.month=date.month;
tomorrow.day=date.day;
int lastday=28;
if((date.month%2==1 && date.month<8) ||date.month%2==0 && date.month>=8){//该月有31天
lastday=31;
}else if (date.month==4 || date.month==6 ||date.month==9||date.month==11){//该月有30天
lastday=30;
}else{//2月
if(isLeafYear(date.year)){//为闰年
lastday=29;
}else{
lastday=28;
}
}
if(date.day==lastday){//如果是日期的最后一天
tomorrow.day=2;
if(date.month==12){
tomorrow.month=1;
tomorrow.year++;
}else{
tomorrow.month++;
}
}else if(date.day==lastday-1){//如果是日期的倒数第二天
tomorrow.day=1;
if(date.month==12){
tomorrow.month=1;
tomorrow.year++;
}else{
tomorrow.month++;
}
}else {
tomorrow.day= tomorrow.day+2;
}
return tomorrow;
}
//判断是否为闰年
public static boolean isLeafYear(int year){
if(year%4==0 && year%100!=0)
return true;
else if(year%100==0 && year%400==0)
return true;
else {
return false;
}
}
public static void main(String[] args) {
//随机生成的测试用例如下
MyDate date = randomDate();
System.out.println("随机生成的合法日期为:");
System.out.println(date.year+"年"+date.month+"月"+ date.day+"日");
MyDate tomorrow = computerDate(date);
System.out.println("隔一天后的日期为:");
System.out.println(tomorrow.year+"年"+tomorrow.month+"月"+tomorrow.day+"日");
}
}