软件测试学习——白盒测试实验报告

软件测试课程作业二

实验内容

  • 编写判断是否为闰年的程序
  • 画出程序的流程图
  • 将流程图转换为控制流图
  • 基于控制流图,设计该程序的不同标准测试用例集合
    • 语句覆盖
    • 判定覆盖
    • 条件覆盖
    • 路径覆盖
    • MC/DC 覆盖

实验过程

判断是否为闰年的程序

main 函数为主体框架,执行判定闰年的核心逻辑函数是bool checkLeapYear(int year)

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
/*
** 程序名:testLeapYear
** 程序功能:从input输入文件中逐行判断年份是否为闰年年份(int范围)
** 作者:Edwardzcn
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<string, string> pss;
#define rep(i, a, n) for (int i = a; i < n; ++i)
#define per(i, a, n) for (int i = n - 1; i >= a; --i)

#define DEBUG_INPUT DEBUG_INPUT

/*
** 枚举类型:CHECK_TYPE
** 枚举说明:用来表征rawInput检测后的类型
** 作者:Edwardzcn
*/
enum CHECK_TYPE
{
NO_NUM,
POSITIVE_NUM,
NEGATIVE_NUM
};

/*
** 函数名:checkInput
** 函数功能:用来检测rawInput
** 参数名称与类型:
** string s
** 返回值:
** int
*/
int checkInput(string s)
{
if (s == "")
return CHECK_TYPE::NO_NUM;

bool flag1 = false;
if (s[0] == '-')
{
flag1 = true;
rep(i, 1, s.size())
{
if (s[i] < '0' || s[i] > '9')
{
return CHECK_TYPE::NO_NUM;
}
}
}
else
{
rep(i, 0, s.size())
{
if (s[i] < '0' || s[i] > '9')
{
return CHECK_TYPE::NO_NUM;
}
}
}
if (flag1)
return CHECK_TYPE::NEGATIVE_NUM;
else
return CHECK_TYPE::POSITIVE_NUM;
}

/*
** 函数名:checkLeapYear
** 函数功能:根据输入年份判断其是否为闰年
** 参数名称与类型:
** int year
** 返回值类型:
** bool
*/
bool checkLeapYear(int year)
{
// 判断闰年的核心逻辑
if (year % 400 == 0 || year % 4 == 0 && year % 100 != 0)
{
return true;
}
return false;
}

/*
** 函数名:main
** 函数功能:主函数,入口
** 参数名称与类型:
** int argc
** char* argv[]
** 返回值类型:
** int
*/
int main(int argc, char *argv[])
{
cout << ("======= Start Program: testLeapYear ==========\n");

#ifdef DEBUG_INPUT
freopen("input", "r", stdin);
// freopen("output", "w", stdout);
#endif // DEBUG_INPUT
string rawInput;
int parseNum;
while (cin >> rawInput)
{
// 调用
int ans = checkInput(rawInput);
switch (ans)
{
case CHECK_TYPE::NO_NUM:
cout << "Input:" << rawInput << " is not a number." << endl;
break;
case CHECK_TYPE::NEGATIVE_NUM:
cout << "Input:" << rawInput << " is not positive." << endl;
break;
case CHECK_TYPE::POSITIVE_NUM:
parseNum = atoi(rawInput.c_str());
if (checkLeapYear(parseNum))
cout << "YES! Year:" << parseNum << " is a leap year." << endl;
else
cout << "NO! Year:" << parseNum << " is not a leap year." << endl;
break;
default:
break;
}
}

cout << "==============================================" << endl;
return 0;
}

绘制流程图

这里只绘制检测闰年的核心逻辑checkLeapYear函数的流程图,框架代码实际上做了更加严密的数据输入格式保证。从 Unit Test 的角度看,我们先进性核心逻辑部分的单元测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
** 函数名:checkLeapYear
** 函数功能:根据输入年份判断其是否为闰年
** 参数名称与类型:
** int year
** 返回值类型:
** bool
*/
bool checkLeapYear(int year)
{
// 判断闰年的核心逻辑
if (year % 400 == 0 || year % 4 == 0 && year % 100 != 0)
{
return true;
}
return false;
}

流程图绘制如下

Lab2_1
Lab2_1

绘制控制流图

将流程图转化为控制流图如下

Lab2_2
Lab2_2

基于控制流图和覆盖准则下的测试用例设计

在核心逻辑代码中,判断语句仅有一个。为方便后续描述,下列定义:

  • 符号\(A\)表示布尔表达式 year % 400 == 0
  • 符号\(B\)表示布尔表达式 year % 4 == 0
  • 符号\(C\)表示布尔表达式 year % 100 != 0

则判断语句形式化为\(A \vee B \wedge C\)。实际上\(A,B,C\)并不是独立的,至少存在下面的蕴含关系: \[A \Rightarrow B\] \[A \Rightarrow \overline{C}\] \[\overline{C} \Rightarrow B\] \[\overline{C} \wedge B \Rightarrow A\] 这在构造时要注意。不是完整的笛卡尔乘积,一个子集(关系)的真值表是成立的。

  • 语句覆盖

    • 概念:每个可执行语句至少执行一次
    • 构造:由于只有单一语句,一组测例输入满足语句覆盖。

      A B C 结果 对应测例
      1 1 0 1 1600
  • 判定覆盖

    • 概念:每个判断的真假值都至少执行一次
    • 构造:由于只有单一语句,那么构造两组测例使结果相反即可。

      A B C 结果 对应测例
      1 1 0 1 1600
      0 1 0 0 200
  • 条件覆盖

    • 概念:每个条件的可能值至少满足一次
    • 构造:由于有\(A,B,C\)三个布尔表达式,构造下表,使得纵列包含\(0,1\)两种取值即可,注意需要满足蕴含关系。

      A B C 结果 对应测例
      1 1 0 1 1600
      0 0 1 0 2007
  • 路径覆盖

    • 概念:所有测试用例,覆盖所有路径

    • 构造:输入可能是枚举不尽的,我就按照全部真值表可能枚举下,下面列出所有真值可能,不符合蕴含关系的在右侧有备注,即不能构造。

      A B C 结果 备注 对应测例
      0 0 0 0 2200
      0 0 1 0 2007
      0 1 0 0 不符合蕴含式 4
      0 1 1 1 2008
      1 0 0 1 不符合蕴含式 1
      1 0 1 1 不符合蕴含式 1、2
      1 1 0 1 1600
      1 1 1 1 不符合蕴含式 2
  • MC/DC 覆盖

    • 概念:改进条件  /判定范围  (MC/DC :ModifiedCondition/Decision Coverage):程序中的每个入口点和出口点至少被调用一次;判定中每个条件的所有取值至少出现一次;每个判定的所有可能结果至少出现一次;每个条件都能独立地影响判定的结果,即在其它所有条件不变的情况下改变该条件的值,使得判定结果改变。
    • 构造:(!注意满足蕴含关系)控制变量的形式,比如先控制\(B,C\)不变,改变\(A\)的值,使得结果值改变(影响判定)。

      A B C 结果 改变量 组别 对应测例
      0 0 1 0 1 2007
      0 1 0 0 不符合蕴含式,无法构造,做对照 2
      0 1 1 1 B:0->1,与组 1 对比 3 2008
      0 1 1 1 C:1->0,与组 2 对比 4 2008
      0 1 0 0 不符合蕴含式,无法构造,做对照 5
      1 1 0 1 A:0->1,与组 5 对比 6 1600

      实际构造数少于理论构造样例数,因为有蕴含关系的限制。

实验结果

以路径覆盖中分析的四种可能真值构造的样例为输入检验程序正确性,运行结果如下图:

Lab2_3
Lab2_3

实验总结

本次实验亲手编程体验了白盒测试的过程,尤其是覆盖原则下的测例、测试集的构造。而且由于闰年条件的相关性(蕴含关系),在手动构建过程中还颇费了一番计算的功夫。通过本次实验,不仅重温了关系运算方面的知识,更加深了对白盒测试的相关概念的理解,增强构造测试集的能力。