三角形问题从需求分析到测试
目录:
三角形问题从需求分析到测试1--需求说明
三角形问题从需求分析到测试2—学生的答案
三角形问题从需求分析到测试3--需求分析
三角形问题从需求分析到测试4—程序设计
三角形问题从需求分析到测试5—编码
三角形问题从需求分析到测试6—单元测试的步骤
三角形问题从需求分析到测试7—单元测试的尝试
三角形问题从需求分析到测试8—构建自己的单元测试框架1
三角形问题从需求分析到测试9—构建自己的单元测试框架2
三角形问题从需求分析到测试10—集成测试的概念
三角形问题从需求分析到测试1—需求说明
三角形的问题在很多软件测试的书籍中都出现过,问题虽小,五脏俱全,是个很不错的软件测试的教学例子。本文借助这个例子结合教学经验,从更高的视角来探讨需求分析、软件设计、软件开发与软件测试之间的关系与作用。
题目:根据下面给出的三角形的需求完成程序并完成测试:
一、输入条件:
1、条件1:a+b>c
2、条件2:a+c>b
3、条件3:b+c>a
4、条件4:0
5、条件5:0
6、条件6:0
7、条件7:a==b
8、条件8:a==c
9、条件9:b==c
10、条件10:a2+b2==c2
11、条件11:a2+c2==b2
12、条件12:c2+b2==a2
二、输出结果:
1、不能组成三角形
2、等边三角形
3、等腰三角形
4、直角三角形
5、一般三角形
6、某些边不满足限制
三角形问题从需求分析到测试2—学生的答案
在教学的过程中发现,很多学生一看到这个需求,都觉得很简单,然后立刻就开始动手写代码了,这并不是一个很好的习惯。如果你的第一直觉也是这样的,不妨耐心看到文章的最后。
大部分学生的思路:
1、首先建立一个main 函数,main 函数第一件事是提示用户输入三角形的三边,然后获取用户的输入(假设用户的输入都是整数的情况),用C 语言来写,这一步基本上不是问题(printf 和scanf ),但是要求用java 来写的话,很多学生就马上遇到问题了,java5.0及之前的版本不容易获取用户的输入。
点评:这样的思路做出来的程序只能通过手工方式来测试所有业务逻辑(测试困难) ,而且这个程序只能是DOS 界面版本了,要是想使用图形化界面来做输入,就得全部写过代码(移植和维护困难) 。
2
、业务处理流程的思路用流程图表示如下:
3、C 语言代码:
#include
void main()
{
int a, b, c;
printf("pleaseenter three integer:");
scanf("%d%d%d",&a,&b,&c);
if(0
{
if(a+b>c&&a+c>b&&c+b>a)
{
if(a==b&&b==c&&a==c)
{
printf("1是等边三角形");
}
else
{
if(a==b||b==c||a==c)
{
printf("2是等腰三角形");
}
else
{
if(a*a+b*b==c*c||a*a+c*c==b*b||b*b+c*c==a*a)
{
printf("3是直角三角形");
}
else
{
printf("4是一般三角形");
}
}
}
}
else
{
printf("5不能组成三角形");
}
}
else
{
printf("6某些边不满足限制");
}//这里可以省掉一个判断
}
点评:这样的思路做出来的程序只能通过手工方式来测试所有业务逻辑,而且这个程序只能是DOS 界面版本了,要是想使用web 或图形化界面来做输入,就得全部写过代码。三角形问题从需求分析到测试3—需求分析
需求分析是后续工作的基石,如果分析思路有问题,后续工作可能就会走向不正确的方向,比如:代码重用性差、难于测试、难于扩展和难于维护等。反而,如果需求分析做的好,对设计、开发和测试来说,都可能是很大的帮助。
看到题目给出的条件达12个之多,粗粗一看,好像很复杂,但仔细分析之后,发现可以把它们分成4组来讨论:
1、条件1:a+b>c;条件2:a+c>b;条件3:b+c>a
这三个表达式有什么特点呢?实际上它们的逻辑是一样的:两个数之和大于第三个数。那么,前面程序的写法就存在逻辑重复的地方,应该把这个逻辑提取到一个函数中。
2、条件4:0
这三个表达式也是同一个逻辑:判断一个数的范围是否在(0, 200)区间内,也应该把这个逻辑提取到一个函数中,去掉重复的逻辑,提高代码的可重用性。
可重用性的好处:比如,现在用户的需求改为了三条边的取值范围要改为[100,400],那么,按前面的思路来说,需要改3个地方,而现在只需要在一个函数里改1个地方,这就是代码重用的好处。
3、条件7:a==b;条件8:a==c;条件9:b==c
这三个表达式的逻辑:判断两个数是否相等。也应该把它提取到一个函数中。
我们进一步来分析一下判断是否是等边三角形或等腰三角形的条件:
(1)前面程序的判断是从最直观的方式(a==b&&b==c&&a==c)(实际上只需要两个表达式成立即可)三条边都相等来判定是等边三角形;(a==b||b==c||a==c)只有两条边相等来判定是等腰三角形。
(2)转变一下思路:给定三个整数,然后用一个函数来判断这三个整数有几个相等,返回相等的个数,如果返回值等于3,那么它是等边三角形,如果返回值是2,那么它是等腰三角形,否则,它是一般三角形(如果不是直角三角形的话)。
4、条件10:a2+b2==c2条件11:a2+c2==b2条件12:c2+b2==a2
这三个条件的处理方式有两种:
(1)跟前面三组分析一样,把相同的逻辑提取到一个函数中,然后三次调用。
(2)根据直角三角形的特点:斜边是最长的,所以我们可以事先写一个函数来找到最长的边,然后把它赋值给c ,这样处理之后,只需要一次调用判定(a2+b2==c2)的函数了。
三角形问题从需求分析到测试4—程序设计
程序设计对于软件的质量和软件实施过程的难易程度起着至关重要的作用。好的设计,即使聘用没什么经验的开发人员都很容易产生出高质量的代码出来;而差的设计,即使是经验很丰富的开发人员也很容易产生缺陷,特别是可重用性、可测试性、可维护性、可扩展性等方面的缺陷。
经过以上的分析,下面来看一下如何设计。在下图中,每个方框都使用一个函数来实现,
为了跟用户界面分开,最顶上的函数不要写在main 函数中。
把思路用流程图的方式表达出来,不用停留在脑袋里:
具体的函数的调用关系图:
复杂模块triangleType 的流程图:
三角形问题从需求分析到测试4—程序设计
程序设计对于软件的质量和软件实施过程的难易程度起着至关重要的作用。好的设计,即使聘用没什么经验的开发人员都很容易产生出高质量的代码出来;而差的设计,即使是经验很丰富的开发人员也很
容易产生缺陷,特别是可重用性、可测试性、可维护性、可扩展性等方面的缺陷。
经过以上的分析,下面来看一下如何设计。在下图中,每个方框都使用一个函数来实现,为了跟用
户界面分开,最顶上的函数不要写在main 函数中。
把思路用流程图的方式表达出来,不用停留在脑袋里:
具体的函数的调用关系图:
复杂模块triangleType 的流程图:
三角形问题从需求分析到测试5—编码
bool isOutOfRange(inti);
/*
*判断三条边是否合法(即:判断三条边都在合法的范围内) *返回值:true-是;false-否
*/
bool isLegal(inta, int b, int c);
/*
*判断两条边之和是否大于第三边
*返回值:true-是;false-否
*/
bool isSumBiger(inta, int b, int c);
/*
*判断三条边是否能够组成三角形
*返回值:true-是;false-否
*/
bool isTriangle(inta, int b, int c);
/*
*判断两条边是否相等
*返回值:true-是;false-否
*/
bool isEquals(inta, int b);
/*
*求三角形有几条边相等
*返回值:相等边的数量
*/
int howManyEquals(inta, int b, int c);
/*
*判断是否满足两边平方之和是否等于第三边的平方*
*/
bool isPowerSumEquals(inta, int b, int c);
/*
*判断第一个数是否比第二个数大
{
return false;
}
else
{
return true;
}
};
/*
*判断三条边是否合法(即:判断三条边都在合法的范围内)
*返回值:true-是;false-否
*/
bool isLegal(inta, int b, int c)
{
if(isOutOfRange(a)||isOutOfRange(b)||isOutOfRange(c))
{
return false;
}
return true;
}
/*
*判断两条边之和是否大于第三边
*返回值:true-是;false-否
*/
bool isSumBiger(inta, int b, int c)
{
if(a+b>c)
{
return true;
}
return false;
}
/*
*判断三条边是否能够组成三角形
*返回值:true-是;false-否
*/
bool isTriangle(inta, int b, int c)
{
if(isSumBiger(a,b, c) &&isSumBiger(a,c, b) &&isSumBiger(b,c, a))
{
return true;
}
return false;
}
/*
*判断两条边是否相等
*返回值:true-是;false-否
*/
bool isEquals(inta, int b)
{
if(a==b)
{
return true;
}
return false;
}
/*
*求三角形有几条边相等
*返回值:相等边的数量
*1:没有边相等2:只有两条边相等3:三条边相等
*
*/
int howManyEquals(inta, int b, int c)
{
int count =1;
if(isEquals(a,b))
{
count++;
}
if(isEquals(b,c))
{
count++;
if(isEquals(a,c))
{
count++;
}
if(count>3) //如果三条边都相等,则count 多加了一次
{
count =3;
}
return count;
}
/*
*判断是否满足两边平方之和是否等于第三边的平方
*
*/
bool isPowerSumEquals(inta, int b, int c)
{
if(a*a+b*b==c*c)
{
return true;
}
return false;
}
/*
*判断第一个数是否比第二个数大
*/
bool isGreaterThan(inta, int b)
{
if(a>b)
{
return true;
}
return false;
}
*判断是否是直角三角形
*
*/
bool isRightRriangle(inta, int b, int c)
{
int max =0;
if(isGreaterThan(a,b))
{
max =a;
a =b;
b =max;
}
if(isGreaterThan(b,c))
{
max =b;
b =c;
c =max;
}
return isPowerSumEquals(a,b, c);
}
/*
*判断三角形的类型,返回值:
*1、不能组成三角形
*2、等边三角形
*3、等腰三角形
*4、直角三角形
*5、一般三角形
*6、某些边不满足限制
*/
int triangleType(inta, int b, int c)
{
int type=0;
if(isLegal(a,b, c))
{
if(isTriangle(a,b, c))
int num =howManyEquals(a,b, c);
if(3==num)
{
type=2;
}
else if(2==num){
type=3;
}
else if(isRightRriangle(a,b, c))
{
type=4;
}
else
{
type=5;
}
}
else
{
type=1;
}
}
else
{
type=6;
}
return type;
}
三角形问题从需求分析到测试6—单元测试的步骤
白盒测试与黑盒测试的过程和方法是有一些区别的。单元测试的步骤:
1、理解需求和设计
理解设计是很重要的,特别是要搞清楚被测试模块在整个软件中所处的位置,这对测试的内容将会有很大的影响。需要记住的一个原则就是:好的设计,各模块只负责完成自己的事情,层次与分工是很明确的。在单元测试的时候,可以不用测试不属于被测试模块所负责的功能,以减少测试用例的冗余,集成测试的时候会有机会测试到的。举例:
/*
*判断三条边是否能够组成三角形
*返回值:true-是;false-否
*/
bool isTriangle(inta, int b, int c);
测试该函数的时候,只需要测试三条边(在合法的取值范围内的整数)是否能够满足两边之和是否大于第三边的功能,而不需要测试三条边是否在合法的范围(0,200)之间的整数,因为调用该函数之前,一定要先通过下面函数的检查,要是检查不通过,就不会执行isTriangle 函数。
/*
*判断三条边是否合法(即:判断三条边都在合法的范围内)
*返回值:true-是;false-否
*/
bool isLegal(inta, int b, int c);
所以,单元测试主要是关注本单元的内部逻辑,而不用关注整个业务的逻辑,因为会有别的模块去完成相关的功能。
2、概览源代码
浏览一下源代码,主要任务:
(1)初步检查源代码的编码风格与规范
(2)大致估算测试工作量,比如:需要多少的测试用例、需要写多少的驱动模块和装模块等。
(3)确定模块的复杂程度,初步制定测试的优先级等。
3、精读源代码
认真阅读和分析代码,主要任务:
(1)理解代码的业务逻辑。
(2)检查代码与设计是否相符,如果详细设计没有该模块的流程图的话,先去画出流程图。
(3)仔细研究逻辑复杂的模块
(4)可以采用一些检查列表来检查程序可能会出现的问题。如果没有检查列表,那么,可以根据程序的特点,有针对性地检查容易出问题的地方(记得把经验总结下来供下次使用)。
4、设计测试用例
综合运用白盒测试方法(和结合黑盒测试方法)来设计测试用例,包括功能测试、性能测试等,要达到一定的测试覆盖率。在设计测试用例的过程中,流程图或控制流图是分析的好帮手。
5、搭建单元测试环境
使用XUnit 或自己写的框架将有助于单元测试的实施。在这个阶段主要就是写桩模块和驱动模块,第4步所设计的测试用例是通过驱动模块传递给被测试模块的,然后驱动模块想办法获取被测试模块对数据的处理结果,并判定返回的实际结果与测试用例的预期结果是否一致,通过测试框架来记录执行的结果,对于出现的错误,还需要统计错误的信息,供执行完之后分析。
搭建单元测试环境要避免在main 函数中使用printf 和scanf 函数来跟测试人员交互来达到获取测试用例数据的信息。这样的测试还是没有摆脱手工测试方式,效率是低下的。同时,对于测试结果是否通过测试也不要使用printf 方式打印被测试函数的返回结果值,避免要人工去检查结果。
6、执行测试
运行写好的驱动模块完成对被测试模块的测试。
7、补充和完善测试用例
单元测试也是个循序渐进的过程,可能一开始考虑的不够全面,或预期的覆盖标准太低,需要在测试过程中不断补充测试用例,直到满足要求为止。
8、分析结果,给出评价
根据测试的结果分析、查找错误的原因,并找到解决的办法。测试结束之后,根据测试过程的数据统计,给出被测试对象评价。
三角形问题从需求分析到测试7—单元测试的尝试
以测试isOutOfRange 函数为例,首先知道该函数在整个软件架构中处于最底层(叶子),所以对它进行测试并不需要写桩模块,只需要写驱动模块。要注意的问题是:对于测试结果是否通过测试不要使用printf 方式打印被测试函数的返回结果值,否则就需要人工去检查结果了。
使用边界值的方法可以得到5个测试用例,写的驱动模块代码如下:
TestTriangle.cpp :
#include"Triangle.h"
/*
*测试isOutOfRange 函数,使用边界值的方法(0,1,5,199,200)
*
*/
void testIsOutOfRange_try()
{
if(isOutOfRange(0)==true)
{
printf("pass!\n");
}
else
{
printf("fail!\n");
}
if(isOutOfRange(1)==false)
{
printf("pass!\n");
}
else
{
printf("fail!\n");
}
}
void main()
{
testIsOutOfRange_try();
}
小知识:做单元测试的时候,一般不直接在main 函数中写所有的测试代码,否则的话,main 函数将会非常庞大。正确的做法:针对每个函数分别创建一个或若干个(函数比较复杂时)测试函数,测试函数的名称习惯以test 开头。
写到这里发现重复的代码太多了,而且如果测试用例数量很多的话,对于测试结果的检查也将是很大的工作量。在测试有错误的时候,这样的单元测试结果也很难获得更多关于错误的信息。
解决问题的途径可以采用cppUnit 单元测试框架。不过这里为了让学生能够对单元测试和单元测试框架有进一步的理解,我决定自己写一个类似cppUnit 的简单的测试框架。三角形问题从需求分析到测试8—构建自己的单元测试框架1
在上一讲“单元测试的尝试”里我们遇到了几个问题:
1、代码重复的问题太多
2、测试结果需要人工去检查
3、对测试的总体信息也无从得知
本讲将构建一个简单的单元测试框架来解决以上的问题:
1、代码重复的问题太多。
这个问题很容易解决,只需要把判断预期结果和实际结果的逻辑提取到某个函数中即可。从整个代码来看,有两种类型的结果的函数:
(1)返回布尔型
(2)返回整数
因此,需要两个类型的判断预期结果和实际结果是否相符的函数:
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual)
{
if(actual)
{
printf(".");
}
else
{
printf("F");
}
}
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual)
{
if(expect==actual)
{
printf(".");
}
else
{
printf("E");
}
}
小知识:XUnit 系列的框架的习惯使用assert*的命名来定义判断函数,对于通过的测试习惯打印一个“.”号,而对于失败的测试习惯打印一个“F”。
2、测试结果需要人工去检查
对于测试结果不要使用printf 方式打印被测试函数的返回结果值就可以避免这个问题。
3、对测试的总体信息也无从得知
除了问题1的解决办法里使用“.”表示测试通过和“F”表示测试失败可以提高对测试结果的信息的直观性之外,做单元测试的人还希望能够得到以下的信息:
(1)执行的测试用例总数、通过的数量和失败的数量
(2)测试执行的时间
(3)如果测试用例执行失败了,希望知道是哪个测试用例失败,从而去分析失败的原因。
下面来实现以上三个目标:
1、为了获取已执行的测试用例数量、通过的数量和失败的数量,以及测试执行的时间,需要定义一些变量来保存这些信息:
/*
*存放测试信息和错误信息的全局变量
*/
int errorCount =0;
int testCount=0;
time_tstartTime, endTime;
2、为了获取失败的测试用例信息,需要先定义一个变量来保存错误信息,同时,上面的两个断言函数也需要增加一个参数,用来传递当失败的时候要显示的失败信息。为了方便记录错误的信息,可以添加两个处理错误信息的函数:
/*
*存放测试信息和错误信息的全局变量
*/
char *errors[100]={""};
int errorCount =0;
int testCount=0;
time_tstartTime, endTime;
两个断言函数:
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual)
{
testCount++;
if(actual)
{
printf(".");
}
else
{
printf("F");
addError(msg);
}
}
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual)
{
testCount++;
if(expect==actual)
{
printf(".");
}
else
{
printf("F");
addError(msg,expect, actual);
}
}
失败信息的处理函数:
/*
*添加错误信息
*/
void addError(char*msg)
{
char error[100]="Test '";
strcat(error,msg);
strcat(error,"' is failed!");
errors[errorCount]=new char[100];
strcpy(errors[errorCount],error);
errorCount ++;
}
/*
*添加错误信息,带预期结果与实际结果参数
*/
void addError(char*msg,int expect, int actual)
{
char error[100]="Test '";
char num[10];
strcat(error,msg);
strcat(error,"' is failed!");
strcat(error," Expected:");
strcat(error,itoa(expect,num, 10));
strcat(error," , actual:");
strcat(error,itoa(actual,num, 10));
errors[errorCount]=new char[100];
strcpy(errors[errorCount],error);
errorCount ++;
}
3、计算测试所花费的时间:
在测试开始之前开始计时,测试结束之后,停止计时。
/*
*初始化测试,开始计时
*/
void init()
{
printf("\n******Test start ******\n");
startTime =clock();
}
/*
*结束测试,停止计时
*/
void end()
{
endTime =clock();
}
4、测试结束之后,把收集到的信息打印出来:
/*
*测试报告
*/
void testReport()
{
printf("\n\nTotalrun Tests:");//测试概要信息
printf("%d",testCount);
printf(",passed:%d",testCount-errorCount);
printf(",failed:%d\n",errorCount);
printf("Testescaped time:%6.3fseconds\n",
(double)(endTime-startTime)/1000.0);
if(errorCount>0)
{//测试失败的详细信息
printf("\n****************Failed Test's Detail ****************\n\n");
for(inti=0;i
{
printf("%d:", i+1);
printf(errors[i]);
printf("\n");
}
printf("\n****************End of Failed Detail ****************\n\n");
}
else
{
printf("\n******All Tests had Passed! ******\n\n");
}
}
把这个函数放在end 函数中调用:
/*
*结束测试,结束计时,打印报告//所有测试都通过
*/
void end()
{
endTime =clock();
testReport();
}
三角形问题从需求分析到测试9—构建自己的单元测试框架2
完整的源代码如下:
1、UnitTest.h
/*
*Copyright (c)2008, 胡添发([email protected])
*
*简单的单元测试框架
*
*/
#include
#include
#include
#include
/*
*VC 中没有sleep 函数,自己写一个
*wait 单位是毫秒
*/
extern void sleep(clock_twait);
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual);
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual); /*
*初始化测试,开始计时
*/
void init();
/*
*结束测试,结束计时,打印报告
*/
void end();
1、UnitTest.cpp
/*
*Copyright (c)2008, 胡添发([email protected])
*
*简单的单元测试框架
*
*详细的教程请访问我的博客:http://hutianfa.blog.163.com*/
#include"UnitTest.h"
/*
*VC 中没有sleep 函数,自己写一个
*wait 单位是毫秒
*/
void sleep(clock_twait)
{
clock_tgoal;
goal =wait +clock();
while(goal >clock())
{
;
}
}
/*
*存放测试信息和错误信息的全局变量
*/
char *errors[100]={""};
int errorCount =0;
int testCount=0;
time_tstartTime, endTime;
/*
*添加错误信息
*/
void addError(char*msg)
{
char error[100]="Test '";
strcat(error,msg);
strcat(error,"' is failed!");
errors[errorCount]=new char[100];strcpy(errors[errorCount],error);
errorCount ++;
}
/*
*添加错误信息,带预期结果与实际结果参数*/
void addError(char*msg,int expect, int actual) {
char error[100]="Test '";
char num[10];
strcat(error,msg);
strcat(error,"' is failed!");
strcat(error," Expected:");
strcat(error,itoa(expect,num, 10));
strcat(error," , actual:");
strcat(error,itoa(actual,num, 10));
errors[errorCount]=new char[100];
strcpy(errors[errorCount],error);
errorCount ++;
}
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual)
{
sleep(10);
testCount++;
if(actual)
{
printf(".");
}
else
{
printf("F");
addError(msg);
}
}
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual)
{
sleep(10);
testCount++;
if(expect==actual)
{
printf(".");
}
else
{
printf("F");
addError(msg,expect, actual);
}
}
/*
*测试报告
*/
void testReport()
{
printf("\n\nTotalrun Tests:");//测试概要信息
printf("%d",testCount);
printf(",passed:%d",testCount-errorCount);
printf(",failed:%d\n",errorCount);
printf("Testescaped time:%6.3fseconds\n",
(double)(endTime-startTime)/1000.0);
if(errorCount>0)
{//测试失败的详细信息
printf("\n****************Failed Test's Detail ****************\n\n");
for(inti=0;i
{
printf("%d:", i+1);
printf(errors[i]);
printf("\n");
}
printf("\n****************End of Failed Detail ****************\n\n");
}
else
{
printf("\n******All Tests had Passed! ******\n\n");
}
}//所有测试都通过
/*
*初始化测试,开始计时
*/
void init()
{
printf("\n******Test
start ******\n");
startTime =clock();
}
/*
*结束测试,结束计时,打印报告
*/
void end(){
endTime =clock();
testReport();
}
实际测试结果截图:
三角形问题从需求分析到测试10—集成测试的概念
一、桩模块和驱动模块(以C 语言为例):
很多人对桩模块和驱动模块的概念会搞不清楚,下面先介绍这两个概念:
模块结构实例图:
假设现在项目组把任务分给了7个人,每个人负责实现一个模块。你负责的是B 模块,你很优秀,第一个完成了编码工作,现在需要开展单元测试工作,先分析结构图:
1、由于B 模块不是最顶层模块,所以它一定不包含main 函数(A 模块包含main 函数),也就不能独立运行。
2、B 模块调用了D 模块和E 模块,而目前D 模块和E 模块都还没有开发好,那么想让B 模块通过编译器的编译也是不可能的。
那么怎样才能测试B 模块呢?需要做:
1、写两个模块Sd 和Se 分别代替D 模块和E 模块(函数名、返回值、传递的参数相同),这样B 模块就可以通过编译了。Sd 模块和Se 模块就是桩模块。
2、写一个模块Da 用来代替A 模块,里面包含main 函数,可以在main 函数中调用B 模块,让B 模块运行起来。Da 模块就是驱动模块。
知识点:
桩模块的使命除了使得程序能够编译通过之外,还需要模拟返回被代替的模块的各种可能返回值(什么时候返回什么值需要根据测试用例的情况来决定)。
驱动模块的使命就是根据测试用例的设计去调用被测试模块,并且判断被测试模块的返回值是否与测试用例的预期结果相符。
二、集成测试策略:
1、非增式集成测试
各个单元模块经过单元测试之后,一次性组装成完整的系统。
优点:集成过程很简单。
缺点:出现集成问题时,查找问题比较麻烦,而且测试容易遗漏。
范例:
2、增式集成测试
(1)自顶向下
A 、纵向优先
从最顶层开始测试,需要写桩模块。测试的顺序:从跟节点开始,每次顺着某枝干到该枝干的叶子节点添加一个节点到已测试好的子系统中,接着再加入另一枝干的节点,直到所有节点集成到系统中。
B 、横向优先
跟纵向优先的区别在于:每次并不是顺着枝干走到叶子,而是逐一加入它的直属子节点。
纵向优先的范例:
(2
)自底向上
每次从叶子节点开始测试,测试过的节点摘掉,然后把树上的叶子节点摘下来加入到已经测试好的子系统之中。优点:不需要写桩模块,但需要写驱动模块。
范例:
三角形问题从需求分析到测试
目录:
三角形问题从需求分析到测试1--需求说明
三角形问题从需求分析到测试2—学生的答案
三角形问题从需求分析到测试3--需求分析
三角形问题从需求分析到测试4—程序设计
三角形问题从需求分析到测试5—编码
三角形问题从需求分析到测试6—单元测试的步骤
三角形问题从需求分析到测试7—单元测试的尝试
三角形问题从需求分析到测试8—构建自己的单元测试框架1
三角形问题从需求分析到测试9—构建自己的单元测试框架2
三角形问题从需求分析到测试10—集成测试的概念
三角形问题从需求分析到测试1—需求说明
三角形的问题在很多软件测试的书籍中都出现过,问题虽小,五脏俱全,是个很不错的软件测试的教学例子。本文借助这个例子结合教学经验,从更高的视角来探讨需求分析、软件设计、软件开发与软件测试之间的关系与作用。
题目:根据下面给出的三角形的需求完成程序并完成测试:
一、输入条件:
1、条件1:a+b>c
2、条件2:a+c>b
3、条件3:b+c>a
4、条件4:0
5、条件5:0
6、条件6:0
7、条件7:a==b
8、条件8:a==c
9、条件9:b==c
10、条件10:a2+b2==c2
11、条件11:a2+c2==b2
12、条件12:c2+b2==a2
二、输出结果:
1、不能组成三角形
2、等边三角形
3、等腰三角形
4、直角三角形
5、一般三角形
6、某些边不满足限制
三角形问题从需求分析到测试2—学生的答案
在教学的过程中发现,很多学生一看到这个需求,都觉得很简单,然后立刻就开始动手写代码了,这并不是一个很好的习惯。如果你的第一直觉也是这样的,不妨耐心看到文章的最后。
大部分学生的思路:
1、首先建立一个main 函数,main 函数第一件事是提示用户输入三角形的三边,然后获取用户的输入(假设用户的输入都是整数的情况),用C 语言来写,这一步基本上不是问题(printf 和scanf ),但是要求用java 来写的话,很多学生就马上遇到问题了,java5.0及之前的版本不容易获取用户的输入。
点评:这样的思路做出来的程序只能通过手工方式来测试所有业务逻辑(测试困难) ,而且这个程序只能是DOS 界面版本了,要是想使用图形化界面来做输入,就得全部写过代码(移植和维护困难) 。
2
、业务处理流程的思路用流程图表示如下:
3、C 语言代码:
#include
void main()
{
int a, b, c;
printf("pleaseenter three integer:");
scanf("%d%d%d",&a,&b,&c);
if(0
{
if(a+b>c&&a+c>b&&c+b>a)
{
if(a==b&&b==c&&a==c)
{
printf("1是等边三角形");
}
else
{
if(a==b||b==c||a==c)
{
printf("2是等腰三角形");
}
else
{
if(a*a+b*b==c*c||a*a+c*c==b*b||b*b+c*c==a*a)
{
printf("3是直角三角形");
}
else
{
printf("4是一般三角形");
}
}
}
}
else
{
printf("5不能组成三角形");
}
}
else
{
printf("6某些边不满足限制");
}//这里可以省掉一个判断
}
点评:这样的思路做出来的程序只能通过手工方式来测试所有业务逻辑,而且这个程序只能是DOS 界面版本了,要是想使用web 或图形化界面来做输入,就得全部写过代码。三角形问题从需求分析到测试3—需求分析
需求分析是后续工作的基石,如果分析思路有问题,后续工作可能就会走向不正确的方向,比如:代码重用性差、难于测试、难于扩展和难于维护等。反而,如果需求分析做的好,对设计、开发和测试来说,都可能是很大的帮助。
看到题目给出的条件达12个之多,粗粗一看,好像很复杂,但仔细分析之后,发现可以把它们分成4组来讨论:
1、条件1:a+b>c;条件2:a+c>b;条件3:b+c>a
这三个表达式有什么特点呢?实际上它们的逻辑是一样的:两个数之和大于第三个数。那么,前面程序的写法就存在逻辑重复的地方,应该把这个逻辑提取到一个函数中。
2、条件4:0
这三个表达式也是同一个逻辑:判断一个数的范围是否在(0, 200)区间内,也应该把这个逻辑提取到一个函数中,去掉重复的逻辑,提高代码的可重用性。
可重用性的好处:比如,现在用户的需求改为了三条边的取值范围要改为[100,400],那么,按前面的思路来说,需要改3个地方,而现在只需要在一个函数里改1个地方,这就是代码重用的好处。
3、条件7:a==b;条件8:a==c;条件9:b==c
这三个表达式的逻辑:判断两个数是否相等。也应该把它提取到一个函数中。
我们进一步来分析一下判断是否是等边三角形或等腰三角形的条件:
(1)前面程序的判断是从最直观的方式(a==b&&b==c&&a==c)(实际上只需要两个表达式成立即可)三条边都相等来判定是等边三角形;(a==b||b==c||a==c)只有两条边相等来判定是等腰三角形。
(2)转变一下思路:给定三个整数,然后用一个函数来判断这三个整数有几个相等,返回相等的个数,如果返回值等于3,那么它是等边三角形,如果返回值是2,那么它是等腰三角形,否则,它是一般三角形(如果不是直角三角形的话)。
4、条件10:a2+b2==c2条件11:a2+c2==b2条件12:c2+b2==a2
这三个条件的处理方式有两种:
(1)跟前面三组分析一样,把相同的逻辑提取到一个函数中,然后三次调用。
(2)根据直角三角形的特点:斜边是最长的,所以我们可以事先写一个函数来找到最长的边,然后把它赋值给c ,这样处理之后,只需要一次调用判定(a2+b2==c2)的函数了。
三角形问题从需求分析到测试4—程序设计
程序设计对于软件的质量和软件实施过程的难易程度起着至关重要的作用。好的设计,即使聘用没什么经验的开发人员都很容易产生出高质量的代码出来;而差的设计,即使是经验很丰富的开发人员也很容易产生缺陷,特别是可重用性、可测试性、可维护性、可扩展性等方面的缺陷。
经过以上的分析,下面来看一下如何设计。在下图中,每个方框都使用一个函数来实现,
为了跟用户界面分开,最顶上的函数不要写在main 函数中。
把思路用流程图的方式表达出来,不用停留在脑袋里:
具体的函数的调用关系图:
复杂模块triangleType 的流程图:
三角形问题从需求分析到测试4—程序设计
程序设计对于软件的质量和软件实施过程的难易程度起着至关重要的作用。好的设计,即使聘用没什么经验的开发人员都很容易产生出高质量的代码出来;而差的设计,即使是经验很丰富的开发人员也很
容易产生缺陷,特别是可重用性、可测试性、可维护性、可扩展性等方面的缺陷。
经过以上的分析,下面来看一下如何设计。在下图中,每个方框都使用一个函数来实现,为了跟用
户界面分开,最顶上的函数不要写在main 函数中。
把思路用流程图的方式表达出来,不用停留在脑袋里:
具体的函数的调用关系图:
复杂模块triangleType 的流程图:
三角形问题从需求分析到测试5—编码
bool isOutOfRange(inti);
/*
*判断三条边是否合法(即:判断三条边都在合法的范围内) *返回值:true-是;false-否
*/
bool isLegal(inta, int b, int c);
/*
*判断两条边之和是否大于第三边
*返回值:true-是;false-否
*/
bool isSumBiger(inta, int b, int c);
/*
*判断三条边是否能够组成三角形
*返回值:true-是;false-否
*/
bool isTriangle(inta, int b, int c);
/*
*判断两条边是否相等
*返回值:true-是;false-否
*/
bool isEquals(inta, int b);
/*
*求三角形有几条边相等
*返回值:相等边的数量
*/
int howManyEquals(inta, int b, int c);
/*
*判断是否满足两边平方之和是否等于第三边的平方*
*/
bool isPowerSumEquals(inta, int b, int c);
/*
*判断第一个数是否比第二个数大
{
return false;
}
else
{
return true;
}
};
/*
*判断三条边是否合法(即:判断三条边都在合法的范围内)
*返回值:true-是;false-否
*/
bool isLegal(inta, int b, int c)
{
if(isOutOfRange(a)||isOutOfRange(b)||isOutOfRange(c))
{
return false;
}
return true;
}
/*
*判断两条边之和是否大于第三边
*返回值:true-是;false-否
*/
bool isSumBiger(inta, int b, int c)
{
if(a+b>c)
{
return true;
}
return false;
}
/*
*判断三条边是否能够组成三角形
*返回值:true-是;false-否
*/
bool isTriangle(inta, int b, int c)
{
if(isSumBiger(a,b, c) &&isSumBiger(a,c, b) &&isSumBiger(b,c, a))
{
return true;
}
return false;
}
/*
*判断两条边是否相等
*返回值:true-是;false-否
*/
bool isEquals(inta, int b)
{
if(a==b)
{
return true;
}
return false;
}
/*
*求三角形有几条边相等
*返回值:相等边的数量
*1:没有边相等2:只有两条边相等3:三条边相等
*
*/
int howManyEquals(inta, int b, int c)
{
int count =1;
if(isEquals(a,b))
{
count++;
}
if(isEquals(b,c))
{
count++;
if(isEquals(a,c))
{
count++;
}
if(count>3) //如果三条边都相等,则count 多加了一次
{
count =3;
}
return count;
}
/*
*判断是否满足两边平方之和是否等于第三边的平方
*
*/
bool isPowerSumEquals(inta, int b, int c)
{
if(a*a+b*b==c*c)
{
return true;
}
return false;
}
/*
*判断第一个数是否比第二个数大
*/
bool isGreaterThan(inta, int b)
{
if(a>b)
{
return true;
}
return false;
}
*判断是否是直角三角形
*
*/
bool isRightRriangle(inta, int b, int c)
{
int max =0;
if(isGreaterThan(a,b))
{
max =a;
a =b;
b =max;
}
if(isGreaterThan(b,c))
{
max =b;
b =c;
c =max;
}
return isPowerSumEquals(a,b, c);
}
/*
*判断三角形的类型,返回值:
*1、不能组成三角形
*2、等边三角形
*3、等腰三角形
*4、直角三角形
*5、一般三角形
*6、某些边不满足限制
*/
int triangleType(inta, int b, int c)
{
int type=0;
if(isLegal(a,b, c))
{
if(isTriangle(a,b, c))
int num =howManyEquals(a,b, c);
if(3==num)
{
type=2;
}
else if(2==num){
type=3;
}
else if(isRightRriangle(a,b, c))
{
type=4;
}
else
{
type=5;
}
}
else
{
type=1;
}
}
else
{
type=6;
}
return type;
}
三角形问题从需求分析到测试6—单元测试的步骤
白盒测试与黑盒测试的过程和方法是有一些区别的。单元测试的步骤:
1、理解需求和设计
理解设计是很重要的,特别是要搞清楚被测试模块在整个软件中所处的位置,这对测试的内容将会有很大的影响。需要记住的一个原则就是:好的设计,各模块只负责完成自己的事情,层次与分工是很明确的。在单元测试的时候,可以不用测试不属于被测试模块所负责的功能,以减少测试用例的冗余,集成测试的时候会有机会测试到的。举例:
/*
*判断三条边是否能够组成三角形
*返回值:true-是;false-否
*/
bool isTriangle(inta, int b, int c);
测试该函数的时候,只需要测试三条边(在合法的取值范围内的整数)是否能够满足两边之和是否大于第三边的功能,而不需要测试三条边是否在合法的范围(0,200)之间的整数,因为调用该函数之前,一定要先通过下面函数的检查,要是检查不通过,就不会执行isTriangle 函数。
/*
*判断三条边是否合法(即:判断三条边都在合法的范围内)
*返回值:true-是;false-否
*/
bool isLegal(inta, int b, int c);
所以,单元测试主要是关注本单元的内部逻辑,而不用关注整个业务的逻辑,因为会有别的模块去完成相关的功能。
2、概览源代码
浏览一下源代码,主要任务:
(1)初步检查源代码的编码风格与规范
(2)大致估算测试工作量,比如:需要多少的测试用例、需要写多少的驱动模块和装模块等。
(3)确定模块的复杂程度,初步制定测试的优先级等。
3、精读源代码
认真阅读和分析代码,主要任务:
(1)理解代码的业务逻辑。
(2)检查代码与设计是否相符,如果详细设计没有该模块的流程图的话,先去画出流程图。
(3)仔细研究逻辑复杂的模块
(4)可以采用一些检查列表来检查程序可能会出现的问题。如果没有检查列表,那么,可以根据程序的特点,有针对性地检查容易出问题的地方(记得把经验总结下来供下次使用)。
4、设计测试用例
综合运用白盒测试方法(和结合黑盒测试方法)来设计测试用例,包括功能测试、性能测试等,要达到一定的测试覆盖率。在设计测试用例的过程中,流程图或控制流图是分析的好帮手。
5、搭建单元测试环境
使用XUnit 或自己写的框架将有助于单元测试的实施。在这个阶段主要就是写桩模块和驱动模块,第4步所设计的测试用例是通过驱动模块传递给被测试模块的,然后驱动模块想办法获取被测试模块对数据的处理结果,并判定返回的实际结果与测试用例的预期结果是否一致,通过测试框架来记录执行的结果,对于出现的错误,还需要统计错误的信息,供执行完之后分析。
搭建单元测试环境要避免在main 函数中使用printf 和scanf 函数来跟测试人员交互来达到获取测试用例数据的信息。这样的测试还是没有摆脱手工测试方式,效率是低下的。同时,对于测试结果是否通过测试也不要使用printf 方式打印被测试函数的返回结果值,避免要人工去检查结果。
6、执行测试
运行写好的驱动模块完成对被测试模块的测试。
7、补充和完善测试用例
单元测试也是个循序渐进的过程,可能一开始考虑的不够全面,或预期的覆盖标准太低,需要在测试过程中不断补充测试用例,直到满足要求为止。
8、分析结果,给出评价
根据测试的结果分析、查找错误的原因,并找到解决的办法。测试结束之后,根据测试过程的数据统计,给出被测试对象评价。
三角形问题从需求分析到测试7—单元测试的尝试
以测试isOutOfRange 函数为例,首先知道该函数在整个软件架构中处于最底层(叶子),所以对它进行测试并不需要写桩模块,只需要写驱动模块。要注意的问题是:对于测试结果是否通过测试不要使用printf 方式打印被测试函数的返回结果值,否则就需要人工去检查结果了。
使用边界值的方法可以得到5个测试用例,写的驱动模块代码如下:
TestTriangle.cpp :
#include"Triangle.h"
/*
*测试isOutOfRange 函数,使用边界值的方法(0,1,5,199,200)
*
*/
void testIsOutOfRange_try()
{
if(isOutOfRange(0)==true)
{
printf("pass!\n");
}
else
{
printf("fail!\n");
}
if(isOutOfRange(1)==false)
{
printf("pass!\n");
}
else
{
printf("fail!\n");
}
}
void main()
{
testIsOutOfRange_try();
}
小知识:做单元测试的时候,一般不直接在main 函数中写所有的测试代码,否则的话,main 函数将会非常庞大。正确的做法:针对每个函数分别创建一个或若干个(函数比较复杂时)测试函数,测试函数的名称习惯以test 开头。
写到这里发现重复的代码太多了,而且如果测试用例数量很多的话,对于测试结果的检查也将是很大的工作量。在测试有错误的时候,这样的单元测试结果也很难获得更多关于错误的信息。
解决问题的途径可以采用cppUnit 单元测试框架。不过这里为了让学生能够对单元测试和单元测试框架有进一步的理解,我决定自己写一个类似cppUnit 的简单的测试框架。三角形问题从需求分析到测试8—构建自己的单元测试框架1
在上一讲“单元测试的尝试”里我们遇到了几个问题:
1、代码重复的问题太多
2、测试结果需要人工去检查
3、对测试的总体信息也无从得知
本讲将构建一个简单的单元测试框架来解决以上的问题:
1、代码重复的问题太多。
这个问题很容易解决,只需要把判断预期结果和实际结果的逻辑提取到某个函数中即可。从整个代码来看,有两种类型的结果的函数:
(1)返回布尔型
(2)返回整数
因此,需要两个类型的判断预期结果和实际结果是否相符的函数:
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual)
{
if(actual)
{
printf(".");
}
else
{
printf("F");
}
}
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual)
{
if(expect==actual)
{
printf(".");
}
else
{
printf("E");
}
}
小知识:XUnit 系列的框架的习惯使用assert*的命名来定义判断函数,对于通过的测试习惯打印一个“.”号,而对于失败的测试习惯打印一个“F”。
2、测试结果需要人工去检查
对于测试结果不要使用printf 方式打印被测试函数的返回结果值就可以避免这个问题。
3、对测试的总体信息也无从得知
除了问题1的解决办法里使用“.”表示测试通过和“F”表示测试失败可以提高对测试结果的信息的直观性之外,做单元测试的人还希望能够得到以下的信息:
(1)执行的测试用例总数、通过的数量和失败的数量
(2)测试执行的时间
(3)如果测试用例执行失败了,希望知道是哪个测试用例失败,从而去分析失败的原因。
下面来实现以上三个目标:
1、为了获取已执行的测试用例数量、通过的数量和失败的数量,以及测试执行的时间,需要定义一些变量来保存这些信息:
/*
*存放测试信息和错误信息的全局变量
*/
int errorCount =0;
int testCount=0;
time_tstartTime, endTime;
2、为了获取失败的测试用例信息,需要先定义一个变量来保存错误信息,同时,上面的两个断言函数也需要增加一个参数,用来传递当失败的时候要显示的失败信息。为了方便记录错误的信息,可以添加两个处理错误信息的函数:
/*
*存放测试信息和错误信息的全局变量
*/
char *errors[100]={""};
int errorCount =0;
int testCount=0;
time_tstartTime, endTime;
两个断言函数:
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual)
{
testCount++;
if(actual)
{
printf(".");
}
else
{
printf("F");
addError(msg);
}
}
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual)
{
testCount++;
if(expect==actual)
{
printf(".");
}
else
{
printf("F");
addError(msg,expect, actual);
}
}
失败信息的处理函数:
/*
*添加错误信息
*/
void addError(char*msg)
{
char error[100]="Test '";
strcat(error,msg);
strcat(error,"' is failed!");
errors[errorCount]=new char[100];
strcpy(errors[errorCount],error);
errorCount ++;
}
/*
*添加错误信息,带预期结果与实际结果参数
*/
void addError(char*msg,int expect, int actual)
{
char error[100]="Test '";
char num[10];
strcat(error,msg);
strcat(error,"' is failed!");
strcat(error," Expected:");
strcat(error,itoa(expect,num, 10));
strcat(error," , actual:");
strcat(error,itoa(actual,num, 10));
errors[errorCount]=new char[100];
strcpy(errors[errorCount],error);
errorCount ++;
}
3、计算测试所花费的时间:
在测试开始之前开始计时,测试结束之后,停止计时。
/*
*初始化测试,开始计时
*/
void init()
{
printf("\n******Test start ******\n");
startTime =clock();
}
/*
*结束测试,停止计时
*/
void end()
{
endTime =clock();
}
4、测试结束之后,把收集到的信息打印出来:
/*
*测试报告
*/
void testReport()
{
printf("\n\nTotalrun Tests:");//测试概要信息
printf("%d",testCount);
printf(",passed:%d",testCount-errorCount);
printf(",failed:%d\n",errorCount);
printf("Testescaped time:%6.3fseconds\n",
(double)(endTime-startTime)/1000.0);
if(errorCount>0)
{//测试失败的详细信息
printf("\n****************Failed Test's Detail ****************\n\n");
for(inti=0;i
{
printf("%d:", i+1);
printf(errors[i]);
printf("\n");
}
printf("\n****************End of Failed Detail ****************\n\n");
}
else
{
printf("\n******All Tests had Passed! ******\n\n");
}
}
把这个函数放在end 函数中调用:
/*
*结束测试,结束计时,打印报告//所有测试都通过
*/
void end()
{
endTime =clock();
testReport();
}
三角形问题从需求分析到测试9—构建自己的单元测试框架2
完整的源代码如下:
1、UnitTest.h
/*
*Copyright (c)2008, 胡添发([email protected])
*
*简单的单元测试框架
*
*/
#include
#include
#include
#include
/*
*VC 中没有sleep 函数,自己写一个
*wait 单位是毫秒
*/
extern void sleep(clock_twait);
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual);
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual); /*
*初始化测试,开始计时
*/
void init();
/*
*结束测试,结束计时,打印报告
*/
void end();
1、UnitTest.cpp
/*
*Copyright (c)2008, 胡添发([email protected])
*
*简单的单元测试框架
*
*详细的教程请访问我的博客:http://hutianfa.blog.163.com*/
#include"UnitTest.h"
/*
*VC 中没有sleep 函数,自己写一个
*wait 单位是毫秒
*/
void sleep(clock_twait)
{
clock_tgoal;
goal =wait +clock();
while(goal >clock())
{
;
}
}
/*
*存放测试信息和错误信息的全局变量
*/
char *errors[100]={""};
int errorCount =0;
int testCount=0;
time_tstartTime, endTime;
/*
*添加错误信息
*/
void addError(char*msg)
{
char error[100]="Test '";
strcat(error,msg);
strcat(error,"' is failed!");
errors[errorCount]=new char[100];strcpy(errors[errorCount],error);
errorCount ++;
}
/*
*添加错误信息,带预期结果与实际结果参数*/
void addError(char*msg,int expect, int actual) {
char error[100]="Test '";
char num[10];
strcat(error,msg);
strcat(error,"' is failed!");
strcat(error," Expected:");
strcat(error,itoa(expect,num, 10));
strcat(error," , actual:");
strcat(error,itoa(actual,num, 10));
errors[errorCount]=new char[100];
strcpy(errors[errorCount],error);
errorCount ++;
}
/*
*判断是否取值为真
*/
void assertTrue(char*msg,bool actual)
{
sleep(10);
testCount++;
if(actual)
{
printf(".");
}
else
{
printf("F");
addError(msg);
}
}
/*
*判断预期结果和实际结果是否相符
*/
void assertEquals(char*msg,int expect, int actual)
{
sleep(10);
testCount++;
if(expect==actual)
{
printf(".");
}
else
{
printf("F");
addError(msg,expect, actual);
}
}
/*
*测试报告
*/
void testReport()
{
printf("\n\nTotalrun Tests:");//测试概要信息
printf("%d",testCount);
printf(",passed:%d",testCount-errorCount);
printf(",failed:%d\n",errorCount);
printf("Testescaped time:%6.3fseconds\n",
(double)(endTime-startTime)/1000.0);
if(errorCount>0)
{//测试失败的详细信息
printf("\n****************Failed Test's Detail ****************\n\n");
for(inti=0;i
{
printf("%d:", i+1);
printf(errors[i]);
printf("\n");
}
printf("\n****************End of Failed Detail ****************\n\n");
}
else
{
printf("\n******All Tests had Passed! ******\n\n");
}
}//所有测试都通过
/*
*初始化测试,开始计时
*/
void init()
{
printf("\n******Test
start ******\n");
startTime =clock();
}
/*
*结束测试,结束计时,打印报告
*/
void end(){
endTime =clock();
testReport();
}
实际测试结果截图:
三角形问题从需求分析到测试10—集成测试的概念
一、桩模块和驱动模块(以C 语言为例):
很多人对桩模块和驱动模块的概念会搞不清楚,下面先介绍这两个概念:
模块结构实例图:
假设现在项目组把任务分给了7个人,每个人负责实现一个模块。你负责的是B 模块,你很优秀,第一个完成了编码工作,现在需要开展单元测试工作,先分析结构图:
1、由于B 模块不是最顶层模块,所以它一定不包含main 函数(A 模块包含main 函数),也就不能独立运行。
2、B 模块调用了D 模块和E 模块,而目前D 模块和E 模块都还没有开发好,那么想让B 模块通过编译器的编译也是不可能的。
那么怎样才能测试B 模块呢?需要做:
1、写两个模块Sd 和Se 分别代替D 模块和E 模块(函数名、返回值、传递的参数相同),这样B 模块就可以通过编译了。Sd 模块和Se 模块就是桩模块。
2、写一个模块Da 用来代替A 模块,里面包含main 函数,可以在main 函数中调用B 模块,让B 模块运行起来。Da 模块就是驱动模块。
知识点:
桩模块的使命除了使得程序能够编译通过之外,还需要模拟返回被代替的模块的各种可能返回值(什么时候返回什么值需要根据测试用例的情况来决定)。
驱动模块的使命就是根据测试用例的设计去调用被测试模块,并且判断被测试模块的返回值是否与测试用例的预期结果相符。
二、集成测试策略:
1、非增式集成测试
各个单元模块经过单元测试之后,一次性组装成完整的系统。
优点:集成过程很简单。
缺点:出现集成问题时,查找问题比较麻烦,而且测试容易遗漏。
范例:
2、增式集成测试
(1)自顶向下
A 、纵向优先
从最顶层开始测试,需要写桩模块。测试的顺序:从跟节点开始,每次顺着某枝干到该枝干的叶子节点添加一个节点到已测试好的子系统中,接着再加入另一枝干的节点,直到所有节点集成到系统中。
B 、横向优先
跟纵向优先的区别在于:每次并不是顺着枝干走到叶子,而是逐一加入它的直属子节点。
纵向优先的范例:
(2
)自底向上
每次从叶子节点开始测试,测试过的节点摘掉,然后把树上的叶子节点摘下来加入到已经测试好的子系统之中。优点:不需要写桩模块,但需要写驱动模块。
范例: