計算某天的下一天:黑盒測試之等價類劃分+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均被輸入,等價類劃分如下。

等價類劃分表.jpg

按照題意則確實按上表這樣劃分等價類;按照實際情況則應考慮2月的特殊性。在本次實驗中,我按照題意劃分等價類;考慮到2月的特殊性,在根據有效等價類和無效等價類設計測試用例之外,我額外設計了4個測試用例

測試用例

有效等價類測試用例

測試數據 期望結果 覆蓋範圍
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月設計的等價類測試用例

因為閏年的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進位
…… …… ……

源程式碼

​ 項目結構如下圖所示

項目結構.jpg

DateUtil.java

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

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個測試用例均測試成功,程式實際輸出與期望值相同。

測試結果.jpg

實驗總結

本次實驗的主要目的是鞏固黑盒測試方法中的等價類劃分法的知識,練習JUnit的參數化測試。在本次實驗中,我認為我的getNextDate函數的實現並不是很優雅,比較過程化。寫這個函數花了我很多時間,主要問題在於我沒有抓住一些關鍵的、抽象的邏輯和子函數,比如天向月份進位和月份向年份完全可以參照加法器的循環、可以寫一個函數根據年份和月份判斷出天數的最大值等等。


作者:@臭鹹魚

轉載請註明出處://www.cnblogs.com/chouxianyu/

歡迎討論和交流!