目录
程序测试的重要性
读者也许已经注意到了,本章的许多程序都对运行结果进行了多次测试,这是为什么呢?
程序测试是确保程序质量的一种有效手段。测试的主要方式是,给出特定的输入,运行被测程序,检查程序的输出是否与预期结果一致。包含所有可能情况的测试,称为穷尽测试。然而,在实际中对输入数据的所有可能取值的所有排列组合都进行测试几乎是不可能的,也是不现实的,因为要考虑时间费用等限制,因此不允许无休止的测试,只能进行抽样检查。所以程序测试只能证明程序有错,而不能证明程序无错(E. W. Dijkstra)。
程序测试的过程实际上就是发现程序错误的过程,程序测试的目的就是为了尽可能多地发现程序中的错误,成功的测试在于发现迄今为止尚未发现的错误。如果程序测试中没有发现任何错误,则可能是测试不充分,没有发现潜在的错误,而不能证明程序没有错误。因此,程序测试能提高程序质量,但提高程序质量不能完全依赖于程序测试。
测试用例
由于进行程序测试需要运行程序,而运行程序需要数据,为测试设计的数据称为测试用例(Test Case)。测试的基本任务是,根据软件开发各个阶段的文档和程序,精心设计测试用例,利用这些测试用例执行程序,找出软件中潜在的各种错误和缺陷。
测试方法
如果程序测试人员对被测试程序的内部结构很熟悉,即被测程序的内部结构和流向是可见的,或者说是已知的,那么可按照程序的内部逻辑来设计测试用例,检验程序中的每条通路是否都能按预定要求工作。这种测试方法称为白盒测试(White Box Testing),或玻璃盒测试(Glass Box Testing),也称为结构测试。这种测试方法选取用例的出发点是:尽量让测试数据覆盖程序中的每条语句,每个分支和每个判断条件,并减少重复覆盖。这种方法主要用于测试的早期。
把系统看成一个黑盒子,不考虑程序内部的逻辑结构和处理过程,只根据需求规格说明书的要求,设计测试用例,检查程序的功能是否符合它的功能说明,这种测试方法称为黑盒测试(Black Box Testing),也称为功能测试。黑盒测试的实质是对程序功能的覆盖性测试,因此可以从程序拟实现的功能出发选取测试用例。这种方法适用于测试的后期。
在实际应用中,通常将白盒测试与黑盒测试结合使用,例如,选择有限数量的重要路径进行白盒测试,对重要的功能需求进行黑盒测试。
示例:三角形类型判断
【例 5.7】编程输入三角形的三条边 a、b、c,判断它们能否构成三角形。若能构成三角形,指出是何种三角形:等腰三角形、直角三角形、还是一般三角形?
【问题求解方法分析】对于构成一个三角形来说,主要需要满足的条件是 “任意两边之和大于第三边”。虽然 “任意两边之差小于第三边” 也是一个正确的条件,但它是第一个条件的推论,因此在判断时不是必需的。所以,这两个条件中,只需要满足第一个条件即可构成三角形。
为了对特殊三角形和一般三角形进行区分,本例中定义了一个标志变量 flag,并将其初始化为 1。如果三角形属于某种特殊类型的三角形,那么就将 flag 置为 0,表示它不再是普通三角形。如果 flag 的值始终为 1,则表示它为普通三角形。
此外,本例中还要注意实数比较的问题。因为实数运算的结果是有精度限制的,判断三角形是否为直角三角形不能使用:
if (a * a + b * b == c * c || a * a + c * c == b * b || c * c + b * b == a * a)
而应该使用例 5.6 所示的方法,即:
if (fabs(a * a + b * b - c * c) <= EPS || fabs(a * a + c * c - b * b) <= EPS || fabs(c * c + b * b - a * a) <= EPS)
本例程序设计中的另一个难点在于,理清各种三角形之间的逻辑关系,它关系到条件语句的合理运用。
对等腰、直角、一般三角形的判断
请问下面的程序错在哪里?
#include <stdio.h>
#include <math.h> // 用于使用 fabs() 函数,计算浮点数的绝对值
// 定义一个非常小的数 EPS,用于浮点数比较时的误差范围
#define EPS 1e-1
int main(void)
{
float a, b, c;
// 提示用户输入三角形的三条边
printf("Input a,b,c: ");
// 注意输入格式,这里假设用户输入的是以逗号分隔的三个浮点数
scanf("%f,%f,%f", &a, &b, &c);
// 构成三角形的条件是任意两边之和大于第三边
if (a + b > c && b + c > a && a + c > b)
{
// 注意实数比较的问题,由于浮点数计算可能存在微小误差,直接比较可能不准确
// 因此,我们使用 fabs 函数计算两个浮点数的差的绝对值,并与 EPS 比较
// 检查是否为等腰三角形:任意两边相等
if (fabs(a - b) <= EPS || fabs(b - c) <= EPS || fabs(c - a) <= EPS)
{
printf("等腰三角形\n");
}
else
{
// 检查是否为直角三角形:满足勾股定理
if (fabs(a * a + b * b - c * c) <= EPS || fabs(a * a + c * c - b * b) <= EPS || fabs(c * c + b * b - a * a) <= EPS)
{
printf("直角三角形\n");
}
else
{
// 既不是等腰三角形也不是直角三角形,则为一般三角形
printf("一般三角形\n");
}
}
}
else
{
// 输入的三条边不能构成三角形
printf("不是三角形\n");
}
return 0;
}
本例采用黑盒测试方法,针对等腰三角形、直角三角形、一般三角形和非三角形 4 种情况,设计了 4 个测试用例,测试结果如下:
使用这 4 个测试用例的测试结果都是正确的,但是这能说明程序一定是正确的吗?
仔细研究发现,还有一种特殊类型的三角形即等腰直角三角形没有测试。现在来补充一个测试用例,即选择输入为 “10, 10, 14.14” 的情况测试一下程序。
这个结果显然是错误的,为什么没有输出 “等腰直角三角形” 的结果呢?分析发现,原因不是直角三角形的判断方法有误,而是因为使用了 if-else 语句来分别判断等腰三角形和直角三角形,导致当程序输入 “10, 10, 14.14” 时,因满足 if 分支的判断条件,而没有执行 else 分支,相当于没有执行直角三角形的判断。
添加对等腰直角三角形的判断
在讨论如何修改程序之前,先来看一下各种三角形之间的关系,如图 5-8 所示。
一般地,只有非此即彼的关系才采用 if-else 语句,而对于有交叉的关系,应使用两个并列的 if 语句。本例中由于等腰三角形、直角三角形不是非此即彼的关系,而是存在交叉,集合相交的部分正是等腰直角三角形,因此不能用 if-else 语句来依次判断是否是等腰三角形和直角三角形,应该用并列的 if 语句来判断。因此,将程序修改为:
#include <stdio.h>
#include <math.h> // 引入数学库,用于使用 fabs() 函数计算浮点数的绝对值
#define EPS 1e-1 // 定义误差范围 EPS,用于比较浮点数是否足够接近
int main(void)
{
float a, b, c; // 定义三角形的三条边
int flag = 1; // 定义一个标志变量 flag,初始化为 1,用于判断三角形类型是否已确定
// 提示用户输入三角形的三条边
printf("Input a,b,c: ");
// 使用 scanf 函数读取用户输入的三条边,注意输入格式要求用逗号分隔
scanf("%f,%f,%f", &a, &b, &c);
// 判断输入的三条边是否能构成一个三角形(任意两边之和大于第三边)
if (a + b > c && b + c > a && a + c > b)
{
// 判断是否为等腰三角形(任意两边之差小于等于 EPS)
if (fabs(a - b) <= EPS || fabs(b - c) <= EPS || fabs(c - a) <= EPS)
{
printf("等腰"); // 输出“等腰”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 注意:这里使用两个并列的 if 语句而不是 else if,因为等腰和直角三角形可能有交叉
// 判断是否为直角三角形(满足勾股定理的逆定理,任意两边的平方和等于第三边的平方,误差在 EPS 范围内)
if (fabs(a * a + b * b - c * c) <= EPS || fabs(a * a + c * c - b * b) <= EPS || fabs(c * c + b * b - a * a) <= EPS)
{
printf("直角"); // 输出“直角”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 如果 flag 仍为 1,则表示三角形既不是等腰也不是直角,为一般三角形
if (flag)
{
printf("一般"); // 输出“一般”
}
// 输出“三角形”以完成句子
printf("三角形\n");
}
else
{
// 如果输入的三条边不能构成三角形,则输出“不是三角形”
printf("不是三角形\n");
}
return 0; // 程序正常结束
}
这时,程序的 5 次测试结果如下:
现在用这 5 个测试用例测试的结果都正确了,但事实上程序还有问题。例如,当输入等边三角形的边长时,只能输出 “等腰三角形”,因为程序中没有进行 “等边三角形” 的判断。
添加对等边三角形的判断
在下面的程序中,增加了是否是等边三角形的判断,根据图 5-8 所示的三角形之间关系,现在来分析一下下面的程序存在什么问题。
#include <stdio.h>
#include <math.h> // 引入数学库,用于fabs()函数计算浮点数的绝对值
#define EPS 1e-1 // 定义误差范围 EPS,用于比较浮点数是否足够接近
int main(void)
{
float a, b, c; // 定义三角形的三条边
int flag = 1; // 标志变量 flag,初始化为 1,用于后续判断三角形类型
// 提示用户输入三角形的三条边
printf("Input a,b,c: ");
// 读取用户输入的三条边,注意输入格式要求用逗号分隔
scanf("%f,%f,%f", &a, &b, &c);
// 判断输入的三条边是否能构成一个三角形(任意两边之和大于第三边)
if (a + b > c && b + c > a && a + c > b)
{
// 判断是否为等腰三角形(任意两边之差小于等于 EPS)
if (fabs(a - b) <= EPS || fabs(b - c) <= EPS || fabs(c - a) <= EPS)
{
printf("等腰"); // 输出“等腰”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定(但这里需要注意,等边也会进入这个判断)
}
// 注意:下面的 else if 实际上永远不会执行,因为等边三角形也会满足上面的等腰条件
// 为了逻辑清晰,应该单独判断等边三角形,或者调整判断顺序
else if (fabs(a - b) <= EPS && fabs(b - c) <= EPS && fabs(c - a) <= EPS)
{
printf("等边"); // 输出“等边”(这行代码实际上在上面的条件下永远不会执行)
flag = 0; // 设置标志变量为 0(这行代码也不会执行)
}
// 判断是否为直角三角形(满足勾股定理的逆定理)
if (fabs(a * a + b * b - c * c) <= EPS || fabs(a * a + c * c - b * b) <= EPS || fabs(c * c + b * b - a * a) <= EPS)
{
printf("直角"); // 输出“直角”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 如果 flag 仍为 1,则表示三角形既不是等腰(等边会被包含在内)也不是直角,为一般三角形
if (flag)
{
printf("一般"); // 输出“一般”
}
// 输出“三角形”以完成句子
printf("三角形\n");
}
else
{
// 如果输入的三条边不能构成三角形,则输出“不是三角形”
printf("不是三角形\n");
}
return 0; // 程序正常结束
}
// 注意:原代码中的 else if 部分用于判断等边三角形是多余的,因为等边三角形也会满足等腰三角形的条件。
// 如果要正确判断等边三角形,应该单独进行,或者在判断等腰三角形之前进行,并在判断等腰三角形时排除等边的情况。
如图 5-8 所示,等边三角形是等腰三角形的一种特例,不是等腰三角形的三角形一定不是等边三角形,但不是等边三角形的三角形却有可能是等腰三角形,因此应该先判断是否为等边三角形,然后再判断是否为等腰三角形,即将 19~32 行语句修改为:
// 首先判断是否为等边三角形(三条边都相等)
if (fabs(a - b) <= EPS && fabs(b - c) <= EPS && fabs(c - a) <= EPS)
{
printf("等边"); // 输出“等边”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 如果不是等边三角形,再判断是否为等腰三角形(任意两边相等)
else if (fabs(a - b) <= EPS || fabs(b - c) <= EPS || fabs(c - a) <= EPS)
{
printf("等腰"); // 输出“等腰”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
完整的程序代码如下所示:
#include <stdio.h>
#include <math.h> // 引入数学库,用于使用 fabs() 函数计算浮点数的绝对值
#define EPS 1e-1 // 定义误差范围 EPS,用于比较浮点数是否足够接近
int main(void)
{
float a, b, c; // 定义三角形的三条边
int flag = 1; // 定义一个标志变量 flag,初始化为 1,用于判断三角形类型
// 提示用户输入三角形的三条边
printf("Input a,b,c: ");
// 使用 scanf 函数读取用户输入的三条边,注意输入格式要求用逗号分隔
scanf("%f,%f,%f", &a, &b, &c);
// 判断输入的三条边是否能构成一个三角形(任意两边之和大于第三边)
if (a + b > c && b + c > a && a + c > b)
{
// 首先判断是否为等边三角形(三条边都相等)
if (fabs(a - b) <= EPS && fabs(b - c) <= EPS && fabs(c - a) <= EPS)
{
printf("等边"); // 输出“等边”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 如果不是等边三角形,再判断是否为等腰三角形(任意两边相等)
else if (fabs(a - b) <= EPS || fabs(b - c) <= EPS || fabs(c - a) <= EPS)
{
printf("等腰"); // 输出“等腰”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 判断是否为直角三角形(满足勾股定理的逆定理)
if (fabs(a * a + b * b - c * c) <= EPS || fabs(a * a + c * c - b * b) <= EPS || fabs(c * c + b * b - a * a) <= EPS)
{
printf("直角"); // 输出“直角”
flag = 0; // 设置标志变量为 0,表示三角形类型已确定
}
// 如果 flag 仍为 1,则表示三角形既不是等边、等腰也不是直角,为一般三角形
if (flag)
{
printf("一般"); // 输出“一般”
}
// 输出“三角形”以完成句子
printf("三角形\n");
}
else
{
// 如果输入的三条边不能构成三角形,则输出“不是三角形”
printf("不是三角形\n");
}
return 0; // 程序正常结束
}
这时,程序的 6 次测试结果如下:
示例:成绩转换(白盒测试)
再来看一个使用白盒测试的例子。
【例 5.8】编程将输入的百分制成绩转换为五分制成绩输出。请通过程序测试,分析下面的程序错在哪里。
#include <stdio.h>
int main(void)
{
// 声明变量 score 用于存储分数,mark 用于存储分数对应的等级
int score, mark;
// 提示用户输入分数
printf("Please enter score: ");
// 从标准输入读取用户输入的分数
scanf("%d", &score);
// 计算分数对应的等级(通过除以 10 取整得到,注意这是整数除法)
mark = score / 10;
// 使用 switch 语句根据等级打印对应的成绩
switch (mark)
{
// 如果分数在 90 到 100 之间(包括 90 和 100),打印 A
case 10:
case 9:
printf("%d--A\n", score); // 打印分数和对应的等级 A
break;
// 如果分数在 80 到 89 之间(包括 80),打印 B
case 8:
printf("%d--B\n", score); // 打印分数和对应的等级 B
break;
// 如果分数在 70 到 79 之间(包括 70),打印 C
case 7:
printf("%d--C\n", score); // 打印分数和对应的等级 C
break;
// 如果分数在 60 到 69 之间(包括 60),打印 D
case 6:
printf("%d--D\n", score); // 打印分数和对应的等级 D
break;
// 如果分数在 0 到 59 之间(包括 0 和 59 ),打印 E
case 5:
case 4:
case 3:
case 2:
case 1:
case 0:
printf("%d--E\n", score); // 打印分数和对应的等级 E
break;
// 如果分数不是 0 到 100 之间的整数(例如负数或超过 100 的数),打印输入错误
default:
printf("Input error!\n"); // 打印输入错误信息
}
return 0; // 程序正常结束
}
// 注意:这个程序仍然是不充分的,因为遗漏了边界条件的测试
// 例如,当程序输入 101 ~ 109 (等级 A)或者 -9 ~ -1(等级 E) 之间的数据
本例采用白盒测试,选取测试用例时,使其尽量覆盖所有分支,于是测试结果如下:
边界测试
上述测试似乎覆盖了程序的所有分支,但事实上仍然是不充分的,因为遗漏了边界条件的测试。例如,当程序输入 101 ~ 109 或者 -9 ~ -1 之间的数据时,测试结果为:
结果显然是不对的,因为第 14 行语句执行的是整数除法运算,由于整数除法的结果仍为整数,所以当输入 101 ~ 109 之间的数据时,mark 值为 10,执行 switch 语句后会打印出 'A',而当输入 -9 ~ -1 之间的数据时,mark 值为 0,执行 switch 语句后会打印出 'E'。
可见,在选用测试用例时,不仅要选用合理的输入数据,还应选用不合理的以及某些特殊的输入数据或者临界的点,对程序进行测试,这称为边界测试(Boundary Testing)。
根据上述分析,将程序修改如下:
#include <stdio.h>
int main(void)
{
// 声明变量,score 用于存储用户输入的分数,mark 用于存储分数对应的等级
int score, mark;
// 提示用户输入分数
printf("Please enter score: ");
// 从标准输入读取用户输入的分数
scanf("%d", &score);
// 使用三元运算符判断分数是否有效(0-100之间),有效则计算等级,无效则将 mark 设置为-1
mark = score < 0 || score > 100 ? -1 : score / 10;
// 使用switch语句根据等级打印对应的成绩
switch (mark)
{
// 如果分数在 90-100 之间(包括 90 和 100,即 mark 为 9 或 10),打印 A
case 10:
case 9:
printf("%d -- A\n", score); // 打印分数和对应的等级 A
break;
// 如果分数在 80-89 之间(即 mark 为 8),打印 B
case 8:
printf("%d -- B\n", score); // 打印分数和对应的等级 B
break;
// 如果分数在 70-79 之间(即 mark 为 7),打印 C
case 7:
printf("%d -- C\n", score); // 打印分数和对应的等级 C
break;
// 如果分数在 60-69 之间(即 mark 为 6),打印 D
case 6:
printf("%d -- D\n", score); // 打印分数和对应的等级 D
break;
// 如果分数在 0-59 之间(即 mark 为 0-5),打印 E
case 5:
case 4:
case 3:
case 2:
case 1:
case 0:
printf("%d -- E\n", score); // 打印分数和对应的等级 E
break;
// 如果分数不是 0-100 之间的整数(即 mark 为 -1),打印输入错误
default:
printf("Input error! Score should be between 0 and 100.\n"); // 打印输入错误信息,并明确指出分数范围
}
return 0; // 程序正常结束
}
现在,对于边界上的值就不会出现之前的错误,运行结果如下所示:
标签:分数,case,程序,控制结构,第五章,第十节,测试,三角形,输入 From: https://blog.csdn.net/qq_53139964/article/details/143184066