《软件测试和质量管理》实验报告三
一、目的和要求
1、掌握单元测试技术,并要求按照单元测试的要求设计测试用例;
2、掌握在Eclipse里进行Junit4测试的技术;
3、根据题目要求编写测试用例;
4、实验结果要求给出测试用例集测试效果比较;
5、撰写实验报告。
二、实验内容
日期问题:
测试以下程序:该程序有三个输入变量month、day、year(month、day和year均为整数值,并且满足:1≤month≤12、1≤day≤31和1900≤ year ≤2050),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期。例如,输入为 2004 年11月30日,则该程序的输出为2004年12月1日。
(1)边界值分析法设计测试用例;
(2)根据划分的有效等价类,给出问题规定的可能采取的操作,画出简化后的决策表。
三、测试用例的编写
3.1 边界值分析法设计测试用例
(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)非数串 |
(2)测试用例
表3-2边界值测试用例表
编号 | 输入参数 | 期望输出 | 边界 | ||
Year | Month | Day | |||
1 | 1899 | 1 | 1 | 年的值不在指定范围内 | year上边界 |
2 | 1900 | 1 | 1 | 1900年1月2日 | year上边界 |
3 | 1901 | 1 | 1 | 1901年1月2日 | year上边界 |
4 | 2000 | 1 | 1 | 2000年1月2日 | year健壮 |
5 | 2049 | 1 | 1 | 2049年1月2日 | year下边界 |
6 | 2050 | 1 | 1 | 2050年1月2日 | year下边界 |
7 | 2051 | 1 | 1 | 年的值不在指定范围内 | year下边界 |
8 | 2023 | 0 | 1 | 月的值不在指定范围内 | month上边界 |
9 | 2023 | 1 | 1 | 2023年1月2日 | month上边界 |
10 | 2023 | 2 | 1 | 2023年2月2日 | month上边界 |
11 | 2023 | 5 | 1 | 2023年5月2日 | month健壮 |
12 | 2023 | 11 | 1 | 2023年11月2日 | month下边界 |
13 | 2023 | 12 | 1 | 2023年12月2日 | month下边界 |
14 | 2023 | 13 | 1 | 月的值不在指定范围内 | month下边界 |
15 | 2023 | 1 | 0 | 日的值不在指定范围内 | day上边界 |
16 | 2023 | 1 | 1 | 2023年1月2日 | day上边界 |
17 | 2023 | 1 | 2 | 2023年1月3日 | day上边界 |
18 | 2023 | 1 | 15 | 2023年1月16日 | day健壮 |
19 | 2023 | 2 | 27 | 2023年2月28日 | 平年2月下边界 |
20 | 2023 | 2 | 28 | 2022年3月1日 | 平年2月下边界 |
21 | 2023 | 2 | 29 | 日的值不在指定范围内 | 平年2月下边界 |
22 | 2020 | 2 | 28 | 2020年2月29日 | 闰年2月下边界 |
23 | 2020 | 2 | 29 | 2020年3月1日 | 闰年2月下边界 |
24 | 2020 | 2 | 30 | 日的值不在指定范围内 | 闰年2月下边界 |
25 | 2023 | 1 | 30 | 2023年1月31日 | 大月day下边界 |
26 | 2023 | 1 | 31 | 2023年2月1日 | 大月day下边界 |
27 | 2023 | 1 | 32 | 日的值不在指定范围内 | 大月day下边界 |
28 | 2023 | 4 | 29 | 2023年4月30日 | 小月day下边界 |
29 | 2023 | 4 | 30 | 2023年5月1日 | 小月day下边界 |
30 | 2023 | 4 | 31 | 日的值不在指定范围内 | 小月day下边界 |
31 | 2023 | 5 | 4 | 2023年5月5日 | 普通 |
3.2 判定表驱动法设计用例
(1)条件桩设计
表3-3 条件桩
条件 | 具体 |
M1 | month:只有30天的小月,如:4、6、9、11月 |
M2 | month:除12月外有31天的大月,如:1、3、5、7、8、10月 |
M3 | month:12月 |
M4 | month:2月 |
D1 | day:1≤day≤27 |
D2 | day:day=28 |
D3 | day:day=29 |
D4 | day:day=30 |
D5 | day:day=31 |
Y1 | year:year是闰年 |
Y2 | year:year是平年 |
(2)动作桩设计
动作 | 不存在 | 日期+1 | 日期置1 | 月份+1 | 月份置1 | 年份+1 |
动作桩 | A0 | A1 | A2 | A3 | A4 | A5 |
(3)判断表设计
规则 | 1 2 3 | 4 | 5 | 6 7 8 9 | 10 | 11 12 13 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 22 |
C1 (month是) | M1 | M1 | M1 | M2 | M2 | M3 | M3 | M4 | M4 | M4 | M4 | M4 | M4 |
C2 (day是) | D1 D2 D3 | D4 | D5 | D1 D2 D3 D4 | D5 | D1 D2 D3 D4 | D5 | D1 | D2 | D2 | D3 | D4 | D4 D5 |
C2 (year是) | − | − | − | − | − | − | − | − | Y1 | Y2 | Y1 | Y2 | − |
A0 (不存在) | √ | √ | √ | ||||||||||
A1 (day+1) | √ | √ | √ | √ | √ | ||||||||
A2 (day=1) | √ | √ | √ | √ | √ | ||||||||
A3 (month+1) | √ | √ | √ | √ | |||||||||
A4 (month=1) | √ | ||||||||||||
A5 (year+1) | √ |
(4)测试用例
用例 编号 | 输入参数 | 期望输出 | ||
Year | Month | Day | ||
1-3 | 2023 | 4 | 23 | 2023年4月24日 |
4 | 2023 | 6 | 30 | 2023年7月1日 |
5 | 2023 | 9 | 31 | 日的值不在指定范围内 |
6-9 | 2023 | 5 | 4 | 2023年5月5日 |
10 | 2023 | 5 | 31 | 2023年6月1日 |
11-14 | 2023 | 12 | 15 | 2023年12月16日 |
15 | 2023 | 12 | 31 | 2024年1月1日 |
16 | 2023 | 2 | 14 | 2023年2月15日 |
17 | 2020 | 2 | 28 | 2020年2月29日 |
18 | 2023 | 2 | 28 | 2023年3月1日 |
19 | 2020 | 2 | 29 | 2020年3月1日 |
20 | 2023 | 2 | 29 | 日的值不在指定范围内 |
21, 22 | 2023 | 2 | 30 | 日的值不在指定范围内 |
四、测试结果的分析
4.1 边界值分析法测试结果
(1)测试代码
package com.softtest.danyuan.test;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import com.softtest.danyuan.*;
@RunWith(Parameterized.class)
public class DanYuanTest {
@Parameter
public String input1;
@Parameter(1)
public String input2;
@Parameter(2)
public String input3;
@Parameter(3)
public String expected;
@Parameters
public static Collection<?> prepareData(){
return Arrays.asList(new Object[][]{
// 边界值测试用例
{"1899", "1", "1", "年的值不在指定范围内"}, //0
{"1900", "1", "1", "下一天是1900年1月2日"}, //1
{"1901", "1", "1", "下一天是1901年1月2日"}, //2
{"1901", "1", "1", "下一天是1901年1月2日"}, //3
{"2000", "1", "1", "下一天是2000年1月2日"}, //4
{"2049", "1", "1", "下一天是2049年1月2日"}, //5
{"2050", "1", "1", "下一天是2050年1月2日"}, //6
{"2051", "1", "1", "年的值不在指定范围内"}, //7
{"2023", "0", "1", "月的值不在指定范围内"}, //8
{"2023", "1", "1", "下一天是2023年1月2日"}, //9
{"2023", "2", "1", "下一天是2023年2月2日"}, //10
{"2023", "5", "1", "下一天是2023年5月2日"}, //11
{"2023", "11", "1", "下一天是2023年11月2日"}, //12
{"2023", "12", "1", "下一天是2023年12月2日"}, //13
{"2023", "13", "1", "月的值不在指定范围内"}, //14
{"2023", "1", "0", "日的值不在指定范围内"}, //15
{"2023", "1", "1", "下一天是2023年1月2日"}, //16
{"2023", "1", "2", "下一天是2023年1月3日"}, //17
{"2023", "1", "15", "下一天是2023年1月16日"}, //18
{"2023", "2", "27", "下一天是2023年2月28日"}, //19
{"2023", "2", "28", "下一天是2023年3月1日"}, //20
{"2023", "2", "29", "日的值不在指定范围内"}, //21
{"2020", "2", "28", "下一天是2020年2月29日"}, //22
{"2020", "2", "29", "下一天是2020年3月1日"}, //23
{"2020", "2", "30", "日的值不在指定范围内"}, //24
{"2023", "1", "30", "下一天是2023年1月31日"}, //25
{"2023", "1", "31", "下一天是2023年2月1日"}, //26
{"2023", "1", "32", "日的值不在指定范围内"}, //27
{"2023", "4", "29", "下一天是2023年4月30日"}, //28
{"2023", "4", "30", "下一天是2023年5月1日"}, //29
{"2023", "4", "31", "日的值不在指定范围内"}, //30
{"2023", "5", "4", "下一天是2023年5月5日"}, //31
});
}
public void DateTests(String input1,String input2,String input3,String expected){
this.input1 = input1;
this.input2 = input2;
this.input3 = input3;
this.expected = expected;
}
@Test
public void testNextDate() {
String result = DanYuan.nextDate(input1,input2,input3);
System.out.println(input1 + '年' + input2 + '月'+ input3 +'日');
System.out.println(result);
System.out.println(this.expected);
System.out.flush();
Assert.assertEquals(this.expected,result);
}
}
(2)结果截图
错误用例:
(3)缺陷分析
现针对上述测试到的错误用例详细分析源代码的错误。
1、分析1:
按实际情况来看,编写的“1899年1月1日”、“2051年1月1日”两个用例的年份超出题给范围,应输出“年的值不在指定范围内”。由此,推断源代码中对于年份范围的判断部分的代码出错。
程序修改:将源程序中“year<1000||year>3000”改为“year<1900||year>20500”。
2、分析2:
按实际情况来看,编写的“2023年1月0日”日期超出题给范围,应输出“日的值不在指定范围内”。由此,推断源代码中对于日期范围的判断部分的代码出错。
在源程序中,没有先判断接收的三个参数是否在指定范围内,而是先根据month进行数据处理,再判断处理后的参数是否在指定范围内。此用例虽然最初的三个参数中day=0不符合指定范围,但程序并不判断,而是在经过数据处理day=1后才判断,此时符合了指定范围让其输出。
程序修改:先判断接收的三个参数是否在指定范围内,再根据month进行数据处理。
3、分析3:
编写的“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)nextDate代码修改
package com.softtest.danyuan;
public class DanYuan {
public static String nextDate(String s_year, String s_month, String s_day) {
//将字符串转为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 + "日");
}
}
}
(5)代码修改后用例测试
代码修正后再次验证,此时所有设计的用例均测试通过。
4.2 判定表驱动法测试结果
(1)测试代码
package com.softtest.danyuan.test;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import com.softtest.danyuan.*;
@RunWith(Parameterized.class)
public class DanYuanTest {
@Parameter
public String input1;
@Parameter(1)
public String input2;
@Parameter(2)
public String input3;
@Parameter(3)
public String expected;
@Parameters
public static Collection<?> prepareData(){
return Arrays.asList(new Object[][]{
// 判定表驱动法用例
{"2023", "4", "23", "下一天是2023年4月24日"}, //0
{"2023", "6", "30", "下一天是2023年7月1日"}, //1
{"2023", "9", "31", "日的值不在指定范围内"}, //2
{"2023", "5", "4", "下一天是2023年5月5日"}, //3
{"2023", "5", "31", "下一天是2023年6月1日"}, //4
{"2023", "12", "15", "下一天是2023年12月16日"}, //5
{"2023", "12", "31", "下一天是2024年1月1日"}, //6
{"2023", "2", "14", "下一天是2023年2月15日"}, //7
{"2020", "2", "28", "下一天是2020年2月29日"}, //8
{"2023", "2", "28", "下一天是2023年3月1日"}, //9
{"2020", "2", "29", "下一天是2020年3月1日"}, //10
{"2023", "2", "29", "日的值不在指定范围内"}, //11
{"2023", "2", "30", "日的值不在指定范围内"}, //12
});
}
public void DateTests(String input1,String input2,String input3,String expected){
this.input1 = input1;
this.input2 = input2;
this.input3 = input3;
this.expected = expected;
}
@Test
public void testNextDate() {
String result = DanYuan.nextDate(input1,input2,input3);
System.out.println(input1 + '年' + input2 + '月'+ input3 +'日');
System.out.println(result);
System.out.println(this.expected);
System.out.flush();
Assert.assertEquals(this.expected,result);
}
}
(2)结果截图
(3)缺陷分析
现针对上述测试到的错误用例详细分析源代码的错误。
按实际情况来看,编写的“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)代码修改
package com.softtest.danyuan;
public class DanYuan {
public static String nextDate(String s_year, String s_month, String s_day) {
//将字符串转为int
int year = Integer.parseInt(s_year);
int month = Integer.parseInt(s_month);
int day = Integer.parseInt((s_day));
boolean flag = false;
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 ("日的值不在指定范围内");
}
if (year >= 1900 && year <= 2050 && month <= 12 && month >= 1 && day <= 31 && day >= {
return ("下一天是" + year + "年" + month + "月" + day + "日");
} else if (year < 1000 || year > 3000) {
return ("年的值不在指定范围内");
} else if (month > 12 || month < 1) {
return ("月的值不在指定范围内");
} else if (day > 31 || day < 1) {
return ("日的值不在指定范围内");
}
return "1";
}
}
(5)代码修改后用例测试
代码修正后再次验证,此时所有设计的用例均测试通过。
五、心得与体会
本次实验,针对nextDate函数,我使用junit5测试工具,利用黑盒测试中的边界值分析法和判定表驱动法设计测试用例,根据测试到的出错用例分析源码,找出代码错误后修正再次验证,最终使设计的测试用例在修正后的nextDate函数中全部通过,成功掌握了用单元测试技术按要求设计测试用例。
边界值分析法是对输入或输出的边界值进行测试的一个黑盒测试方法,该方法是对等价类划分法的一个补充,其测试设计基于有效等价类。其基本思想:在最小值(min)、略高于最小值(min+)、正常值(nom)、最大值(max)、略低于最大值(max-)处取输入变量值,对系统进行验证。经验表明,大量的缺陷是发生在输入输出的边界值,而不是输入输出的范围值。如果输入条件规定了值的范围,则应取刚达到这个范围的边界值以及刚刚超过这个范围边界的值作为测试输入数据。如果输入条件规定了值的个数,则用最大个数、最小个数和比最大个数多1个、比最小个数少1个的数作为测试数据。找到每个输入条件的边界点,即可得到边界。基于单边界原则设计测试用例,即每个边界点及领域测试数据设计测试用例。
另外基于普通边界值还需要设计健壮性边界值分析的测试用例,将其中居中的数据也拿来保证健壮性测试,如year除了边界值1和12月外,还可以拿6月这个居中值加入到测试用例当作健壮性测试。
此次实验中,利用边界值分析法我发现源程序存在的问题有:① 年份范围的判断部分的代码出错;② 没有先判断接收的三个参数是否在指定范围内;③ 对2月日期判断部分代码出错。并且通过修改代码,再次检验用例均成功通过测试。
判定表驱动法表示的是有多个输入和多个输出,而且输入与输入之间有相互的组合关系、输入和输出之间有相互的制约和依赖关系。此次实验中,利用判定表驱动法发现源程序对2月日期判断部分代码出错。并且通过修改代码,再次检验用例均成功通过测试。
两种方法测试用例集测试效果比较:基于边界值分析法的测试用例集着重测试输入和输出的边界情况;而基于判定表驱动法的测试用例集能够将复杂的问题按照各种可能的情况全部列举出来,简明并避免遗漏。一般情况下,边界值分析法设计出的测试用例发现程序错误的能力更强,比如本次实验中利用边界值分析法发现了三处代码错误,而判定表驱动法只发现了一处错误。而判定表驱动法更适合于针对不同逻辑条件的组合值需要分别执行不同的操作的数据处理问题,经过化简相比边界值测试用例集要小。