《软件测试和质量管理》实验报告二
一、目的和要求
1、掌握应用黑盒测试技术进行测试用例设计;
2、掌握对测试用例进行优化设计方法。
二、实验内容
日期问题:
测试以下程序:该程序有三个输入变量month、day、year(month、day和year均为整数值,并且满足:1≤month≤12、1≤day≤31和1900≤year≤2050),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期。例如,输入为2004年11月30日,则该程序的输出为2004年12月1日。
(1)划分等价类,按照等价类划分法设计测试用例;
(2)编写nextDate函数;
(3)掌握Junit4的用法,使用Junit4测试nextDate函数。
JUnit4 是 JUnit 框架有史以来的最大改进,其主要目标便是利用 Java5 的 Annotation 特性简化测试用例的编写。掌握 Junit4 定义 的一些常见 Annotations:
org.junit.Test
org.junit.Before
org.junit.After
org.junit.BeforeClass
org.junit.AfterClass
org.junit.Ignore
org.junit.runner.RunWith
org.junit.runners.Suite.SuiteClasses
org.junit.runners.Parameterized.Parameters;
实验要求:
(1)根据题目要求编写测试用例;
(2)准备nextDate函数,使用Junit4测试执行测试;
(3)撰写实验报告。
三、测试用例的编写
3.1 划分等价类并编号
本题按照三个输入变量年year、月month、日day的输入条件:均为整数值并满足1≤month≤12、1≤day≤31和1900≤year≤2050,程序输出条件:需要有对平闰年、大小月、跨年月的测试,进行等价类划分,形成等价类表,并为每一等价类规定唯一编号。最终得到的等价类表如下表3-1所示。
表3-1等价类表
输入数据 | 有效等价类 | 无效等价类 |
年(Year) | (1)1900~2050之间的平年 (2)1900~2050之间的闰年 | (11)Year<1900 (12)Year>2050 (13)非数串 |
月(Month) | (3)Month=2(2月) (4)Month={1,3,5,7,8,10}(大月) (5)Month=12(跨年月) (6)Month={4,6,9,11}(小月) | (14)Month<1 (15)Month>12 (16)非数串 |
日(Day) | (7)1~28所有日(正常) (8)Day=29(闰年2月) (9)Day=30(小月) (10)Day=31(大月) | (17)Day<1 (18)Day>28(平年2月) (19)Day>29(闰年2月) (20)Day>30(小月) (21)Day>31(大月) (22)非数串 |
3.2 为等效有价类设计测试用例
设计一测试用例,使其尽可能多地覆盖尚未覆盖的有效等价类,重复这一步骤,直到所有有效等价类均被测试用例所覆盖。
表3- 2等价有效类测试用例表
序号 | 测试用例 描述 | 输入参数 | 期望输出 | 覆盖范围 | ||
Year | Month | Day | ||||
1 | 有效等价类 | 2023 | 2 | 27 | 2023年2月28日 | 1,3,7 |
2 | 有效等价类 | 2020 | 2 | 29 | 2020年3月1日 | 2,3,8 |
3 | 有效等价类 | 2023 | 3 | 31 | 2023年4月1日 | 1,4,10 |
4 | 有效等价类 | 2023 | 4 | 30 | 2023年5月1日 | 1,6,9 |
5 | 有效等价类 | 2022 | 12 | 31 | 2023年1月1日 | 1,5,10 |
3.3 为无效有价类设计测试用例
设计一新测试用例,使其只覆盖一个无效等价类,重复这一步骤直到所有无效等价类被覆盖。
表3- 2测试用例表
序号 | 测试用例 描述 | 输入参数 | 期望输出 | 覆盖范围 | ||
Year | Month | Day | ||||
1 | 无效等价类 | 1800 | 1 | 1 | 年的值不在指定范围内 | 11 |
2 | 无效等价类 | 2100 | 1 | 1 | 年的值不在指定范围内 | 12 |
3 | 无效等价类 | x | 1 | 1 | 无效输入日期 | 13 |
4 | 无效等价类 | 2023 | 0 | 1 | 月的值不在指定范围内 | 14 |
5 | 无效等价类 | 2023 | 13 | 1 | 月的值不在指定范围内 | 15 |
6 | 无效等价类 | 2023 | x | 1 | 无效输入日期 | 16 |
7 | 无效等价类 | 2023 | 2 | 0 | 日的值不在指定范围内 | 17 |
8 | 无效等价类 | 2023 | 2 | 29 | 日的值不在指定范围内 | 18 |
9 | 无效等价类 | 2020 | 2 | 30 | 日的值不在指定范围内 | 19 |
10 | 无效等价类 | 2023 | 4 | 31 | 日的值不在指定范围内 | 20 |
11 | 无效等价类 | 2023 | 3 | 32 | 日的值不在指定范围内 | 21 |
12 | 无效等价类 | 2023 | 4 | x | 无效输入日期 | 22 |
四、测试结果的分析
4.1 编写测试代码
上一次实验中,我是通过assertEquals一个个判断用例,一旦测试用例变多就会显得冗余,因此这次实验利用参数化测试。
参数化测试分为以下步骤:
- 引入相关包、类;
- 更改测试运行器为Runwith;
- 声明变量用来存放预期值与结果值;
- 声明一个返回值为Collection的公共静态方法,并使用@Parameters进行修饰;
- 为测试类声明一个带有参数的公共构造方法,并在其中为声明变量赋值。
具体代码如下:
package com.softtest.heihe.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
import com.softtest.heihe.*;
@RunWith(Parameterized.class)
public class HeiHeTest1 {
private String input1;
private String input2;
private String input3;
private String expected;
@Parameters
public static Collection<?> prepareData(){
String [][] object = {
// 有效等价类
{"2023","2","27","下一天是2023年2月28日"},
{"2020","2","29","下一天是2020年3月1日"},
{"2023","3","31","下一天是2023年4月1日"},
{"2023","4","30","下一天是2023年5月1日"},
{"2022","12","31","下一天是2023年1月1日"},
// 无效等价类
{"1800","1","1","年的值不在指定范围内"},
{"2100","1","1","年的值不在指定范围内"},
{"x","1","1","无效输入日期"},
{"2023","0","1","月的值不在指定范围内"},
{"2023","13","1","月的值不在指定范围内"},
{"2023","x","1","无效输入日期"},
{"2023","2","0","日的值不在指定范围内"},
{"2023","2","29","日的值不在指定范围内"},
{"2020","2","30","日的值不在指定范围内"},
{"2023","4","31","日的值不在指定范围内"},
{"2023","3","32","日的值不在指定范围内"},
{"2023","4","x","无效输入日期"}
};
return Arrays.asList(object);
}
public HeiHeTest1(String input1,String input2,String input3,String expected){
this.input1 = input1;
this.input2 = input2;
this.input3 = input3;
this.expected = expected;
}
@Test
public void testDate(){
String result = HeiHe1.nextDate(input1,input2,input3);
Assert.assertEquals(expected,result);
}
}
4.2 测试结果分析
根据黑盒测试结果,查看编写的测试用例,发现以下3个测试用例error,4个测试用例failure,其余测试用例均通过测试,没有错误。
其中用例7、10、16为error,其余4个failure用例如下所示(按用例、运行结果、预期结果以行输出):
4.3 缺陷分析
现针对下述测试到的错误用例详细分析源代码的错误。
- {"1800", "1", "1", "年的值不在指定范围内"},
- {"2100", "1", "1", "年的值不在指定范围内"},
- {"x", "1", "1", "无效输入日期"},
- {"2023", "x", "1", "无效输入日期"},
- {"2023", "2", "0", "日的值不在指定范围内"},
- {"2023", "2", "29", "日的值不在指定范围内"},
- {"2023", "4", "x", "无效输入日期"}
(1)分析1:对于年份范围的判断部分的代码出错
按实际情况来看,编写的“1800年1月1日”、“2100年1月1日”两个用例的年份超出题给范围,应输出“年的值不在指定范围内”。由此,推断源代码中对于年份范围的判断部分的代码出错。
程序修改:将源程序中“year<1000||year>3000”改为“year<1900||year>20500”。
(2)分析2:缺少对非数串输入的判断
按实际情况来看,编写的“x年1月1日”、“2023年x月1日”、“2023年4月x日”三个用例包含字符串输入,应当输出无效输入日期。说明在字符串输入时,程序没有检测到string类型的情况,而只考虑了用户只可能输入int类型的数字。由此,推断源程序缺少对非数串输入的判断。
程序修改:增加对于无效字符串的判断。
(3)分析3:没有先判断接收的三个参数是否在指定范围内
按实际情况来看,编写的“2023年2月0日”日期超出题给范围,应输出“日的值不在指定范围内”。由此,推断源代码中对于日期范围的判断部分的代码出错。
在源程序中,没有先判断接收的三个参数是否在指定范围内,而是先根据month进行数据处理,再判断处理后的参数是否在指定范围内。此用例虽然最初的三个参数中day=0不符合指定范围,但程序并不判断,而是在经过数据处理day=1后才判断,此时符合了指定范围让其输出。
程序修改:先判断接收的三个参数是否在指定范围内,再根据month进行数据处理。
(4)分析4:对2月日期判断部分代码出错
按实际情况来看,编写的“2023年2月29日”用例,由于平年没有2月29日,应当输出日的值不在指定范围内。由此,通过结果推断源代码对2月日期判断部分代码出错。
在2月判别部分代码中,程序只有检测到day==28时,才会进入平年闰年的判断。从而导致,在平年情况下判断2月29日的下一天时,直接进入了分支判断day==29,程序输出结果为当年的3月1日,而不是“日的值不在指定范围内”。
程序修改:在2月部分中先判断平年闰年,而后根据年份再选择具体日期的判断。若为闰年,当day=29时,输出下一天为当年的3月1日;当day<29时,输出日期+1;其余情况均为日的值不在指定范围内。若为平年,当day=28时,输出下一天为当年的3月1日;当day<28时,输出日期+1;其余情况均为日的值不在指定范围内。
4.4 修改nextDate函数
package com.softtest.heihe;
import java.util.regex.Pattern;
public class HeiHe1 {
private static final Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
public static String nextDate(String s_year, String s_month, String s_day) {
if (!(isInteger(s_year) && isInteger(s_month) && isInteger(s_day))) {
return "无效输入日期";
}
//将字符串转为int
int year = Integer.parseInt(s_year);
int month = Integer.parseInt(s_month);
int day = Integer.parseInt((s_day));
boolean flag = false;
if (year < 1900 || year > 2050) {
return ("年的值不在指定范围内");
} else if (month > 12 || month < 1) {
return ("月的值不在指定范围内");
} else if (day > 31 || day < 1) {
return ("日的值不在指定范围内");
}
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
if (day == 31) {
day = 1;
month = month + 1;
}
else {
day = day + 1;
}
break;
case 4:
case 6:
case 9:
case 11:
if (day == 30) {
day = 1;
month = month + 1;
}
else if (day == 31) {
flag = true;
}
else {
day = day + 1;
}
break;
case 12:
if (day == 31) {
day = 1;
month = 1;
year = year + 1;
}
else {
day = day + 1;
}
break;
case 2: {
if (((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) {
// 闰年
if (day == 29) {
day = 1;
month = 3;
}
else if (day < 29) {
day = day + 1;
}
else {
flag = true;
}
} else {
//平年
if (day == 28) {
day = 1;
month = 3;
}
else if (day < 28) {
day = day + 1;
}
else {
flag = true;
}
}
}
break;
default:
}
if (flag) {
return ("日的值不在指定范围内");
}
else {
return ("下一天是" + year + "年" + month + "月" + day + "日");
}
}
public static boolean isInteger(String str) {
return pattern.matcher(str).matches();
}
}
4.5 修正nextDate函数后的测试执行结果
代码修正后再次验证,此时所有设计的用例均测试通过。
五、心得与体会
本次实验,针对nextDate函数,我使用junit5测试工具,利用黑盒测试中的等价类划分法设计测试用例,根据测试到的出错用例分析源码,找出代码错误后修正再次验证,最终使设计的测试用例在修正后的nextDate函数中全部通过。
在实验前,我已经按照实验要求设计完成了关于次日日期判断的等价类及其测试用例。根据三个输入数据年、月、日,结合日期的实际情况,我将年份区分为平年和闰年;又将月区分为大月(31日)、小月(30日),另外将12月(跨年月)、2月(复杂月)单独区分出来;而日期则根据月份进一步划分为5个,1~27日是所有月都有的,28日、29日、30日、31日,分别需要单独判断,成为有效等价类之一。这样区分完后,可以设计出我们的有效等价类测试用例组合和针对不同无效等价类设计一个测试用例,能够对程序进行测试。
上机时,因为第一次实验课以及上学期《软件工程》已经接触,上手Junit并不困难,本次实验的关键点在于运用参数化测试,不再通过assertThat/assertEquals一个个判断,一旦测试用例变多显得十分冗余。参数化测试分为以下步骤:① 引入相关包、类;② 更改测试运行器为Runwith;③ 声明变量用来存放预期值与结果值;④ 声明一个返回值为Collection的公共静态方法,并使用@Parameters进行修饰;⑤ 为测试类声明一个带有参数的公共构造方法,并在其中为声明变量赋值。
在编写完测试代码后,我就对被测试类进行了测试。等价分析法测试的结果显示7个用例出错,经过分析,发现源程序存在以下错误:① 对于年份范围的判断部分的代码出错;② 缺少对非数串输入的判断;③ 没有先判断接收的三个参数是否在指定范围内;④ 对2月日期判断部分代码出错。在对它们进行修正后,再次测试,所有用例顺利通过。
在进行黑盒测试时,我发现它只是定位到出错的是哪一部分,而不像白盒测试那样可以精准定位是某一句程序出了错误。但是黑盒测试相比白盒测试更加泛用,对代码量偏大的程序能够有效降低测试的难度。比如本次实验2月这部分代码,通过黑盒测试能知道这个功能点处存在问题,但也只能粗略确定代码段,具体是哪一句出错了,还得靠自己去分析确定。