计算某天的下一天:黑盒测试之等价类划分+JUnit参数化测试

题目要求

测试以下程序:该程序有三个输入变量month、day、year(month、day和year均为整数值,并且满足:1≤month≤12、1≤day≤31和1900≤year≤2050),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期。例如,输入为2004年11月30日,则该程序的输出为2004年12月1日。

  1. 划分等价类,按照等价类划分法设计测试用例;
  2. 编写getNextDate函数;
  3. 掌握Junit4的用法,使用Junit4测试getNextDate函数。

等价类表

假设输入格式为year,month,day,且三个输入变量year、month和day均被输入。

year要区分闰年和平年,其中闰年还可以分为世纪闰年和普通闰年,且year要属于[1900,2050]。

month要根据该月有几天来进行区分,并且需要考虑是否向year进位,且month要属于[1,12]。

day要根据月份来判断天数是否合法,并且需要考虑是否向month进位,且day要属于[1,31]。

等价类划分如下。

等价类划分表.jpg

测试用例

有效等价类测试用例

共有5个有效等价类测试用例。

测试数据 期望结果 覆盖范围
2004/12/25 2004/12/26 2,10,14
2001/2/28 2001/3/1 3,7,15
2000/2/29 2000/3/1 1,7,16
2001/4/30 2001/5/1 3,8,17
2001/5/31 2001/6/1 3,9,18

无效等价类测试用例

共有12个有效等价类测试用例。

测试数据 期望结果 覆盖范围
1899/6/1 year非法 4
2051/6/1 year非法 5
a/6/1 year非法 6
1999/0/1 month非法 11
1999/13/1 month非法 12
1999/a/1 month非法 13
1999/1/0 day非法 19
1999/1/32 day非法 20
1999/1/a day非法 21
2001/2/29 day非法 22
2000/2/30 day非法 23
2001/4/31 day非法 24

源代码

项目结构如下图所示

项目结构.jpg

DateUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package com.company;

public class DateUtil {
// 有31天的月份
private static int[] monthOfThirtyOne = new int[]{1,3,5,7,8,10,12};
// 有30天的月份
private static int[] monthOfThirty = new int[]{4,6,9,11};
// 年月日
private int year;
private int month;
private int day;

// 最终实现的功能,输入是一个“年/月/日”格式的字符串;
// 如果函数运行成功,输出则是相同格式的下一天,否则是错误信息
public String getNextDate(String dateStr){
String updateResult = this.updateDate(dateStr);
// 如果输入合法
if (updateResult.equals("success")){
String checkResult = this.checkDate();
// 如果输入合法
if (checkResult.equals("valid")){
// 计算明天的日期
return this.calcNextDate();
}
return checkResult;
}
return updateResult;
}

// 根据输入字符串转换并更新年月日
private String updateDate(String dateStr){
// 获取年月日
String[] numbers = dateStr.split("/");
try{
this.year = Integer.parseInt(numbers[0]);
}catch (NumberFormatException e){
return "year非法";
}
try{
this.month = Integer.parseInt(numbers[1]);
}catch (NumberFormatException e){
return "month非法";
}
try{
this.day = Integer.parseInt(numbers[2]);
}catch (NumberFormatException e){
return "day非法";
}
return "success";
}

// 检查日期是否合法
private String checkDate(){
String valid = "valid";
String yearInvalid = "year非法";
String monthInvalid = "month非法";
String dayInvalid = "day非法";
// year合法
if (year>=1900&&year<=2050){
// month合法
if (month>=1&&month<=12){
// day小于1
if (day<=0){
return dayInvalid;
}
// 至此能保证day大于0

// 是2月
if (month==2){
// 闰年
if (yearIsLeap(year)){
// 1-29
if (day<=29){
return valid;
}else{
return dayInvalid;
}
}
// 平年2月
else{
// 1-28
if (day<=28){
return valid;
}else{
return dayInvalid;
}
}
}

// 至此能保证不是2月

// 是否为31天的月
for(int i=0;i<7;++i){
if (month==monthOfThirtyOne[i]){
// 1-31
if (day<=31){
return valid;
}else{
return dayInvalid;
}
}
}

// 至此能保证不是2月和31天的月

// 是否为30天的月
for(int i=0;i<4;++i){
if (month==monthOfThirty[i]){
// 1-30
if (day<=30){
return valid;
}else{
return dayInvalid;
}
}
}
}
// month非法
else{
return monthInvalid;
}
}

// year非法
return yearInvalid;
}

// 计算下一天
private String calcNextDate(){
int yearNext;
int monthNext;
int dayNext=day+1;
int dayCarry=0;
int monthCarry=0;

// 处理day
// 是2月
if (month==2){
// 闰年
if (yearIsLeap(year)){
// 1-29
if (day==29){
dayNext = 1;
dayCarry = 1;
}
}
// 平年2月
else{
// 1-28
if (day==28){
dayNext = 1;
dayCarry = 1;
}
}
}
// 不是2月
else{
boolean isThirtyOne= false;
// 是否为31天的月
for(int i=0;i<7;++i){
if (month==monthOfThirtyOne[i]){
isThirtyOne = true;
// 1-31
if (day==31){
dayNext = 1;
dayCarry = 1;
}
break;
}
}

// 至此能保证是30天的月
if (!isThirtyOne){
// 1-30
if (day==30){
dayNext = 1;
dayCarry = 1;
}
}
}

// 处理月
if (month+dayCarry>12){
monthNext = 1;
monthCarry = 1;
}else{
monthNext = month+dayCarry;
}

// 处理年
yearNext = year+monthCarry;

return yearNext +"/"+ monthNext +"/"+ dayNext;
}

// 判断某一年是否为闰年
private boolean yearIsLeap(int year){
// 普通闰年和世纪闰年
if ((year%4==0&&year%100!=0)||(year%400==0)){
return true;
}

// 平年
return false;
}
}

DateUtilTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.test;

import com.company.DateUtil;

import static org.junit.Assert.*;
import org.junit.Test;

//1、参数化测试:引入相关的包和类
import java.util.Collection;
import java.util.Arrays;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class) //2、参数化测试:更改测试运行器为RunWith(Parameterized.class)
public class DateUtilTest {
//3、参数化测试:声明变量用来存放预期值与结果值
private DateUtil util = new DateUtil();
private String date;
private String except;

//4、参数化测试:声明一个返回值为 Collection 的公共静态方法,并使用@Parameters 进行修饰
@Parameters
public static Collection data(){
return Arrays.asList(new Object[][]{
{"2004/12/25", "2004/12/26"},
{"2001/2/28", "2001/3/1"},
{"2000/2/29", "2000/3/1"},
{"2001/4/30", "2001/5/1"},
{"2001/5/31", "2001/6/1"},
{"1899/6/1", "year非法"},
{"2051/6/1", "year非法"},
{"a/6/1", "year非法"},
{"1999/0/1", "month非法"},
{"1999/13/1", "month非法"},
{"1999/a/1", "month非法"},
{"1999/1/0", "day非法"},
{"1999/1/32", "day非法"},
{"1999/1/a", "day非法"},
{"2001/2/29", "day非法"},
{"2000/2/30", "day非法"},
{"2001/4/31", "day非法"},
});
}

//5、参数化测试:为测试类声明一个带有参数的公共构造方法,并在其中为声明变量赋值
public DateUtilTest(String date, String except){
this.date = date;
this.except = except;
}

@Test
public void testGetNextDate(){
assertEquals(except, util.getNextDate(date));
}
}

测试结果

如下图所示,17个测试用例均测试成功,程序实际输出与期望值相同。

测试结果.jpg

实验总结

本次实验的主要目的是巩固黑盒测试方法中的等价类划分法的知识,练习JUnit的参数化测试。在本次实验中,我认为我的getNextDate函数的实现并不是很优雅,比较过程化。写这个函数花了我很多时间,主要问题在于我没有抓住一些关键的、抽象的逻辑和子函数,比如天向月份进位和月份向年份完全可以参照加法器的循环、可以写一个函数根据年份和月份判断出天数的最大值等等。


作者:@臭咸鱼

转载请注明出处:https://www.cnblogs.com/chouxianyu/

欢迎讨论和交流!