一、单元测试
1.定义: 单元测试是对软件基本组成单元进行的测试,如函数(function或procedure)或一个类的方法(method)。
2.单元定义:单元具体有一些基本属性,如明确的功能、规格定义,明确的与其他部分的接口定义等,可清晰地与同一程序的其他单元划分。
在传统的结构化编程语言中,如C语言,进行测试的单元一般是函数或子过程;在面向对象的语言中,如C++,进行测试的基本单元是类或类的方法。
3.基本单元:基本单元不一定是指一个具体的函数(function或procedure)或一个类的方法(method),在具体实现时,也可能对应的是多个程序文件中的一组函数。
4.对象:文档和代码。
5.目的:文档和需求是否一致/代码本身的逻辑检测/文档和代码的一致性。
例1:一个求绝对值函数,文档:当输入参数x>=0时,return x,否则return -x; 代码中写成x>0时,return x,否则return -x;
例2:对于定义的指针变量,不初始化就直接引用。该指针变量所指向的地址是运行时随机产生的,该地址又可能是只能系统访问的内存地址或者其它软件所使用的内存地址,这种情况下会产生错误;
二、单元测试关注重点
1.单元的接口(静态测试)
单元接口就是输入和输出对应关系的集合;对单元进行动态测试无非就是给这个单元一个输入,然后检查输出是否和预期一样。
如果数据不能正常的输入和输出,单元测试就无从谈起,因此需要对单元接口进行如下的测试:
a.被测单元的输入输出参数在个数、属性、顺序上是否和详细设计中的描述保持一致→代码实现应和设计保持一致,接口处容易被忽略;
b.是否修改了只做输入用的形式参数→不要修改只做输入用的形式参数,否则可能会导致数据的错误修改;
c.约束条件是否通过形式参数来传送→避免约束条件通过形式参数传送,防止单元之间的控制耦合;
2.局部数据结构---开发工具
单元的局部数据结构是最常见的错误来源,应设计测试用例以检查以下各种错误:
a.检查不正确或不一致的数据类型;
b.指针未初始化;
c.定义的数据类型不正确,如int a=65536;
d.拷贝过程中出现的错误,如Num1拷贝后变成Num2;
e.强制转型,导致精度丢失,如int a;float b;a=b会导致精度丢失;
3.独立路径
对基本执行路径和循环进行测试会发现大量的错误。设计测试用例查找由于错误的计算、不正确的比较或不正常的控制流面导致的错误:
a.运算的优先次序不正确或误解了运算的优先次序,如:a=b+c>>2不等同于a=b+c/4;
b.运算的方式错误; a=b++;和a=++b不同;
c.不同数据类型的比较;unsigned int a;int b;while(a>b);
d."差1错",不正确的多循环或少循环一次;for(i=0;i<=100;i++)循环101次而不是100次;
e.错误的或不可能的循环终止条件;while(|a|<0),导致死循环;
f.关系表达式中不正确的变量和比较符;如if(x==1)写成if(x=1)
g.当遇到发散的迭代时不能终止的循环,所以迭代要慎用,调用层次不能过深;
h.不适当的修改了循环变量等;while(i);
4.边界条件---覆盖率测试层面
边界上出现错误是常见的,例如:在n次循环的第n次,取最大最小值时容易发生错误;特别要注意数据流,控制流中刚好等于、大于、小于确定的比较值时出现错误的可能性。
int a[10];
for (i=1;i<=10;i++)
{
sum=sum+a[i];
}
5.出错处理---描述准确与否
比较完善的单元设计要求能预见出错的条件,并设置适当的出错处理,以便在程序出错时,能对出错程序重新做安排,保证其逻辑上的正确性。
a.出错的描述不具体或者难以理解,如用户登录界面,输入用户名密码后出现error code00001;
b.出错的描述不足以对错误定位和确定出错的原因,如登陆失败;
c.显示的错误与实际不符,如用户名输错,却显示密码错误;
d.对错误条件的处理不正确,如虽然显示密码错误,但点击确定后仍可登陆;
e.对错误进行处理之前,错误条件已经引起系统的干预等,如严重错误导致操作系统干预;
三、如何进行单元测试
1.单元测试环境
一个类调用另一个类:导入(import)/实例化(New)
驱动单元:
a.接收测试数据,包含测试用例输入和预期输出;
b.把测试用例输入传送给要测试的单元;
c.将被测单元的实际输出和预期输出进行比较,得到测试结果;
d.将测试结果输出到指定位置;
如:测试加法函数
void driver(){
int sum=0;
sum=add(1,1); //b
if (2==sum) //c
printf("ok!
"); //d
else
printf("fail!
");
}
2.单元测试策略
a.孤立的测试策略
方法:不考虑每个模块与其他模块之间的关系,为每个模块设计桩模块和驱动模块。每个模块进行独立的单元测试,如下图所示:
优点:简单易操作;可以达到较高覆盖率;可以并行开展;纯粹的单元测试;
缺点:驱动函数和桩函数工作量大,效率低;
b.自顶向下的单元测试策略
方法:先对最顶层的单元进行测试,把顶层所调用的单元做成桩模块。其次对第二层进行测试,使用上面已经测试的单元做驱动模块。依此类推直到测试完所有模块。
优点:可以节省驱动函数的开发工作量,测试效率较高;
缺点:随着被测单元一个一个被加入,测试过程将变得越来越复杂,并且开发和维护的成本将增加;
c.自底向上测试
方法:先对模块调用层次图上最低层的模块进行单元测试,模拟调用该模块的模块做驱动模块,然后再对上面一层做单元测试,用下面已被测试过的模块做桩模块。依此类推,直到测试完所有模块。
优点:可以节省桩函数的开发工作量,测试效率较高;
缺点:不是纯粹的单元测试,底层函数的测试质量对上层函数的测试将产生很大的影响;
四、单元测试的四个阶段
1.单元测试计划阶段:完成单元测试计划
2.单元测试设计阶段:完成单元测试方案
3.单元测试实现阶段:完成单元测试用例、单元测试规程、单元测试脚本及数据文件
4.单元测试执行阶段:执行单元测试用例,修改发现的问题并进行回归测试,提交单元测试报告
五、单元测试原则
1.对全新的代码或修改多的代码进行单元测试。
2.单元测试根据单元测试计划和方案进行, 排除测试的随意性。
3.必须保证单元测试计划、单元测方案、单元测试用例等经过评审。
4. 当测试用例的测试结果与预期结果不一致时,单元测试的执行人员需如实记录实际的测试结果。
5.只有当测试计划中的结束标准达到时,单元测试才能结束。
6.对被测单元需达到一定的代码覆盖率要求。