C语言程序的测试
程序调试的任务是排除程序中的错误,使程序能顺利地运行并得到预期的效果。程序的调试阶段不仅要发现和消除语法上的错误,还要发现和消除逻辑错误和运行错误。除了可以利用编译时提示的“出错信息”来发现和改正语法错误外,还可以通过程序的测试来发现逻辑错误和运行错误。
程序的测试任务是尽力寻找程序中可能存在的错误。在测试时要设想到程序运行时的各种情况,测试在各种情况下的运行结果是否正确。
有时程序在某些情况下能正确运行,而在另外一些情况下不能正常运行或得不到正确的结果,因此,一个程序即使通过编译并正常运行而且可以得到正确的结果,还不能认为程序就一定没有问题了。要考虑是否在任何情况下都能正常运行并且得到正确的结果。测试的任务就是要找出那些不能正常运行的情况和原因。下面通过一个例子来说明。
求一元二次方程ax2+bx+c=0的根。
有人根据求根公式:x1,2=,编写出以下程序:
#include
#include
Void main()
{
float a,b,c,disc.x1,x2;
scanf(“%f,%f,%f”,&a,&b,&c);
disc=b*b-4*a*c;
x1=(-b+sqrt(disc))/(2*a);
x2=(-b-sqrt(disc))/(2*a);
printf("x1=%6.2f,x2=%6.2f"x1,x2);
Return 0;
}
当输入a,b,c的值为1,-2,-15时,输出x1的值为5,x2的值为-3.结果是正确无误的。但若是输入a,b,c的值为3,2,4时,屏幕上出现“出错信息”,程序停止运行,原因是对负数求平方根了(b2-4ac=4-48=-44<0).
因此,此程序只适用于b2-4ac≧0的情况。不能说上面的程序是错的,而只能说程序“考虑不周”,不是在任何情况下都是正确的。使用这个程序必须满足一定的前提(b2-4ac≧0),这样,就给使用程序的人带来不便。在输入数据前,必须先算一下,b2-4ac是否大于或等于0.
应要求一个程序能适应各种不同的情况,并且都能正常运行并得到相应的结果。
下面分析一下求方程ax2+bx+c=0的根,有几种情况:
(1)a≠0时:
b2-4ac>0,方程有两个不等的实根:x1,2=;
b2-4ac=0,方程有两个相等的实根:x1=x2=;
b2-4ac<0,方程有两个不等的共轭复根:x1,2=。
(2)a=0时,方程就变成一元一次的线性方程:bx+c=0.
当b≠0时,x=-c/b;
当b=0时,方程变为:0x+c=0.
·当c=0时,x可以为任何值;
·当c≠0时,x无解。
综合起来,共有6种情况:
a≠0,b2-4ac>0;
a≠0,b2-4ac=0;
a≠0,b2-4ac<0;
④a=0,b≠0;
⑤a=0,b=0,c=0;
⑥a=0,b=0,c≠0。
应当分别测试程序在以上6种情况下的运行情况,观察它们是否符合要求。为此,应准备6组数据。用这6组数据去测试程序的“健壮性”。在使用上面这个程序时,显然只有满足情况的数据才能使程序正确运行,而输入~⑥情况的数据时,程序出错。这说明程序不“健壮”。为此,应当修改程序,使之能适应以上6种情况。可将程序改为:
#include
#include
Void main()
{
float a,b,c,disc.x1,x2,p,q;
printf(“input a,b,c:”);
scanf(“%f,%f,%f”,&a,&b,&c);
if(a==0)
if(b==0)
if(c==0)
printf(“It is trivial.”) ;
else
printf(“It is impossbile.”) ;
else
printf(“It has one solution:”);
printf(“x=%6.2f”,-c/b);
else
{
disc=b*b-4*a*c;
if(disc>=0)
if(disc>0)
{
printf(“It has two real solutions:”)
x1=(-b+sqrt(disc))/(2*a);
x2=(-b-sqrt(disc))/(2*a);
printf("x1=%6.2f,x2=%6.2f"x1,x2);
}
else
{
printf(“It has two same real solutions:”);
printf(“x1=x2=%6.2f”,-b/(2*a));
}
else
{
printf(“It has two complex solutions:”);
p=-b/(2*a);
q=sqrt(-disc)/(2*a);
printf(“x1=%6.2f+%6.2fi,x2=%6.2f-%6.2fi”,p,q,p,q);
}
}
}
为了测试程序的“健壮性”,我们准备了6组数据:
3,4,1;1,2,1;4,2,1;④0,3,4;⑤0,0,0;⑥0,0,5
分别用这6组数据作为输入的a、b、c的值,得到以下的运行结果:
input a,b,c:3,4,1↙
It has two real solutions:
x1=-0.33,x2=-1.00
input a,b,c:1,2,1↙
It has two same real solutions:
x1=x2=-1.00
input a,b,c:4,2,1↙
It has two complex solutions:
x1=-0.25+0.43i,2=-0.25-0.43i
④input a,b,c:0,3,4↙
It has one solution:
X=-1.33
⑤input a,b,c:0,0,0↙
It is trivial.
⑥input a,b,c:0,0,5↙
It is impossbile.
经过测试,可以看到程序对任何输入的数据都能正常运行并得到正确的结果。
以上是根据数学知识知道输入数据有6种方案。但在有些情况下,并没有现成的数学公式作依据,例如一个商品管理程序,要求对各种不同的检索作出相应的反应。如果程序包含多条路径(如由if语句形成的分支),则应当设计多组测试数据,使程序中每一条路径都有机会执行,观察其运行是否正常。
测试的关键是正确地准备测试数据。如果只准备4组测试数据,程序都能正常运行,仍然不能认为此程序已无问题。只有将程序运行时所有可能的情况都做过测试,才能作出判断。
测试的目的是检查程序有无“漏洞”。对于一个简单的程序,要找出其运行时全部可能执行到的路径,并正确地准备数据并不困难。但是如果需要测试一个复杂的大程序,要找到全部可能的路径并准备出所需的测试数据并非易事。例如,有两个非嵌套的if语句,每个if语句有两个分支,它们所形成的路径数目为2x2=4。如果一个程序包含100个非嵌套的if语句,每个if语句有两个分支则可能的路径数目为2100≈1.267651x1030。实际上进行测试的只是其中一部分(执行几率最高的部分)。因此,经过测试的程序一般来说还不能轻易宣布“没有问题”,只能说:“经过测试的部分无问题”。正如检查身体一样,经过内科、外科、眼科、五官科等各科例行检查后,不能宣布被检查者“没有任何病症”一样,他可能有隐蔽的、不易查出的病症。所以医院的诊断书一般写:“未发现异常”,而不能写“此人身体无任何问题”。
读者应当了解测试的目的,学会组织测试数据,并根据测试的结果完善程序。
应当说,写完一个程序只能说完成任务的一半(甚至不到一半)。调试程序往往比写程序更难,更需要精力、时间和经验。常常有这样的情况:写程序用一天就完成了,而调试程序两三天也未能完成。有时一个小小的程序会出错五六处,而发现和排除一个错误,有时竟需要半天,甚至更多。希望读者通过实践掌握调试程序的方法和技术。