软件测试和质量管理——实验2:黑盒测试

《软件测试和质量管理》实验报告二

一、目的和要求

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 缺陷分析

现针对下述测试到的错误用例详细分析源代码的错误。

  1. {"1800", "1", "1", "年的值不在指定范围内"},
  2. {"2100", "1", "1", "年的值不在指定范围内"},
  3. {"x", "1", "1", "无效输入日期"},
  4. {"2023", "x", "1", "无效输入日期"},
  5. {"2023", "2", "0", "日的值不在指定范围内"},
  6. {"2023", "2", "29", "日的值不在指定范围内"},
  7. {"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月这部分代码,通过黑盒测试能知道这个功能点处存在问题,但也只能粗略确定代码段,具体是哪一句出错了,还得靠自己去分析确定。

### 黑盒测试概述 黑盒测试是一种软件测试方法,该方法不依赖于代码的具体实现细节,而是依据软件的标准用户需求来验证软件的正确性完整性[^1]。在此过程中,测试人员仅关注输入与输出及其在各种条件下的响应。 #### 测试适用范围 通常情况下,黑盒测试被认为适用于各个开发周期内的多个阶段,甚至包括单元测试阶段。尽管有人认为单元测试更适合使用白盒技术,但实际上,在适当的情境下应用黑盒策略同样能够有效地发现潜在错误并提高产品质量[^2]。 ### 设计有效的黑盒测试案例 为了创建高效的黑盒测试方案,建议遵循以下几个原则: - **理解业务逻辑**:深入研究应用程序的功能特性,确保所编写的每一个测试场景都能反映真实的用户体验。 - **边界值分析**:特别注意临界点附近的数值处理,因为这些地方往往是程序最容易出现问题的地方之一。 - **等价类划分**:将大量可能的数据分为几个合理的类别,并从中选取代表性样本作为实际使用的参数集。 - **因果图模型化**:通过图形方式表达输入变量之间的关系模式,进而推导出更全面详尽的状态转移路径。 - **正交试验设计**:借助统计学原理优化组合配置,使得少量精心挑选出来的实例足以覆盖广泛的变化情形[^5]。 ```python def test_black_box_function(input_data): expected_output = calculate_expected_result_based_on_requirements(input_data) actual_output = black_box_under_test.process_input(input_data) if actual_output == expected_output: print(f"Test passed for input {input_data}") else: print(f"Test failed! Expected '{expected_output}', but got '{actual_output}'") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阮阮的阮阮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值