测试以下程序:该程序有三个输入变量month、day、year(month、day和year均为整数值,并且满足:1≤month≤12、1≤day≤31和1900≤year≤2050),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期。例如,输入为2004年11月30日,则该程序的输出为2004年12月1日。
假设输入格式为year,month,day,且三个输入变量year、month和day均被输入,等价类划分如下。
测试数据 | 期望结果 | 覆盖范围 |
---|---|---|
2004/11/30 | 2004/12/1 | 1,5,9 |
测试数据 | 期望结果 | 覆盖范围 |
---|---|---|
1899/6/1 | year非法 | 2 |
2051/6/1 | year非法 | 3 |
a/6/1 | year非法 | 4 |
1999/0/1 | month非法 | 6 |
1999/13/1 | month非法 | 7 |
1999/a/1 | month非法 | 8 |
1999/1/0 | day非法 | 10 |
1999/1/32 | day非法 | 11 |
1999/1/a | day非法 | 12 |
因为闰年的2月有29天、非闰年的2月有28天,所以题目中对于year、month和day三个变量的要求不够严谨,所以再设计4个用例。
测试数据 | 期望结果 | 覆盖范围 |
---|---|---|
2000/2/30 | day非法 | 闰年day非法 |
2000/2/29 | 2000/3/1 | 闰年day合法 |
1999/2/29 | day非法 | 非闰年day非法 |
1999/2/28 | 1999/3/1 | 非闰年day合法 |
上文中的等价类测试用例其实只测试了输入是否合法,而并未关注程序的功能是否实现。因此可以从功能角度出发设计测试用例,举例如下。
测试数据 | 期望结果 | 覆盖范围 |
---|---|---|
2000/12/31 | 2001/1/1 | 31天的月份,month进位,day进位 |
…… | …… | …… |
项目结构如下图所示
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; } }
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/11/30", "2004/12/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非法"}, {"2000/2/30", "day非法"}, {"2000/2/29", "2000/3/1"}, {"1999/2/29", "day非法"}, {"1999/2/28", "1999/3/1"}, {"2000/12/31", "2001/1/1"}, }); } //5、参数化测试:为测试类声明一个带有参数的公共构造方法,并在其中为声明变量赋值 public DateUtilTest(String date, String except){ this.date = date; this.except = except; } @Test public void testGetNextDate(){ assertEquals(except, util.getNextDate(date)); } }
如下图所示,15个测试用例均测试成功,程序实际输出与期望值相同。
本次实验的主要目的是巩固黑盒测试方法中的等价类划分法的知识,练习JUnit的参数化测试。在本次实验中,我认为我的getNextDate函数的实现并不是很优雅,比较过程化。写这个函数花了我很多时间,主要问题在于我没有抓住一些关键的、抽象的逻辑和子函数,比如天向月份进位和月份向年份完全可以参照加法器的循环、可以写一个函数根据年份和月份判断出天数的最大值等等。
作者: @臭咸鱼
转载请注明出处: https://www.cnblogs.com/chouxianyu/
欢迎讨论和交流!