实验五 单元测试
一、实验目的
1)掌握单元测试的方法
2) 学习XUnit测试原理及框架;
3)掌握使用测试框架进行单元测试的方法和过程。
二、实验内容与要求
1、了解单元测试的原理与框架
1.1 单元测试原理
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。
单元测试的内容包括
模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试
(1)模块接口测试
模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。模块接口测试也是集成测试的重点,这里进行的测试主要是为后面打好基础。测试接口正确与否应该考虑下列因素:
-输入的实际参数与形式参数的个数是否相同
-输入的实际参数与形式参数的属性是否匹配
-输入的实际参数与形式参数的量纲是否一致
-调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;
-调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;
-调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;
-调用预定义函数时所用参数的个数、属性和次序是否正确;
-是否存在与当前入口点无关的参数引用;
-是否修改了只读型参数;
-对全程变量的定义各模块是否一致;
-是否把某些约束作为参数传递。
如果模块功能包括外部输入输出,还应该考虑下列因素:
-文件属性是否正确;
-OPEN/CLOSE语句是否正确;
-格式说明与输入输出语句是否匹配;
-缓冲区大小与记录长度是否匹配;
-文件使用前是否已经打开;
-是否处理了文件尾;
-是否处理了输入/输出错误;
-输出信息中是否有文字性错误。
-局部数据结构测试;
-边界条件测试;
-模块中所有独立执行通路测试;
(2)局部数据结构测试
检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确,局部功能是整个功能运行的基础。重点是一些函数是否正确执行,内部是否运行正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:
-不合适或不相容的类型说明;
-变量无初值;
-变量初始化或省缺值有错;
-不正确的变量名(拼错或不正确地截断);
-出现上溢、下溢和地址异常。
(3)边界条件测试
边界条件测试是单元测试中最重要的一项任务。众所周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。边界条件测试是一项基础测试,也是后面系统测试中的功能测试的重点,边界测试执行的较好,可以大大提高程序健壮性。
(4)独立路径测试
在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。测试目的主要是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。具体做法就是程序员逐条调试语句。常见的错误包括:
-误解或用错了算符优先级;
-混合类型运算;
-变量初值错;
-精度不够;
-表达式符号错。
(5)错误处理测试
检查模块的错误处理功能是否包含有错误或缺陷。例如,是否拒绝不合理的输入;出错的描述是否难以理解、是否对错误定位有误、是否出错原因报告有误、是否对错误条件的处理不正确;在对错误处理之前错误条件是否已经引起系统的干预等。
通常单元测试在编码阶段进行。在源程序代码编制完成,经过评审和验证,确认没有语法错误之后,就开始进行单元测试的测试用例设计。利用设计文档,设计可以验证程序功能、找出程序错误的多个测试用例。对于每一组输入,应有预期的正确结果。
1.2 测试框架
xUnit是各种代码驱动测试框架的统称,这些框架可以测试 软件的不同内容(单元),比如函数和类。xUnit框架的主要优点是,它提供了一个自动化测试的解决方案。可以避免多次编写重复的测试代码。
TestCase(具体的测试用例)去使用framwork
TestCase执行后会有TestResult
使用TestSuite控制TestCase的组合
TestRunner执行器,负责执行case
TestListener过程监听,监听case成功失败以及数据结果,输出到结果报告中
Unit测试框架包括四个要素:
(1)测试目标(对象)
一组认定被测对象或被测程序单元测试成功的预定条件或预期结果的设定。Fixture就是被测试的目标,可以是一个函数、一组对象或一个对象。 测试人员在测试前应了解被测试的对象的功能或行为。
(2)测试集
测试集是一组测试用例,这些测试用例要求有相同的测试Fixture,以保证这些测试不会出现管理上的混乱。
(3)测试执行
单个单元测试的执行可以按下面的方式进行:
第一步 编写 setUp() 函数,目的是:建立针对被测试单元的独立测试环境;举个例子,这可能包含创建临时或代理的数据库、目录,再或者启动一个服务器进程。
第二步 编写所有测试用例的测试体或者测试程序;
第三步 编写tearDown()函数,目的是:无论测试成功还是失败,都将环境进行清理,以免影响后续的测试;
(4)断言
断言实际上就是验证被测程序在测试中的行为或状态的一个函数或者宏。断言的失败会引发异常,终止测试的执行。
1.3 面向特定语言的,基于xUnit框架的自动化测试框架
Junit : 主要测试用Java语言编写的代码
CPPunit:主要测试用C++语言编写的代码shi
unittest , PyUnit:主要测试用python语言编写的代码
MiniUnit: 主要用于测试C语言编写的代码
三、实验过程
我和队友的做的选题是四则运算,同时采用的语言是C++语言,所以测试用的是cppunit框架
(1)cppunit框架安装过程
我在官网https://sourceforge.net/projects/cppunit/files/cppunit/下载的cppunit
解压到本地
打开解压包的CPPunitlibraies.dsw文件
然后在里面创建Dsplugln工程结构
通过一系列的环境配置,将cppunit编译成功
然后在lib文件夹下面打开所需文件
cppunit编译安装成功,这一步难度比较大,我是在其他同学的帮助下完成的
(2)测试过程
源码
#include<iostream.h>
#include<stdlib.h>//atof函数的头文件
#include<stdio.h>//sprintf函数的头文件
#define MAX 255//宏定义常量
#include<string.h>
double cmjia(char x[],char y[]);//声明函数
double cmjian(char x[],char y[]);
double cmcheng(char x[],char y[]);
double cmchu(char x[],char y[]);
double compute(char pstr[]);
double xsd(char x[]);
//主函数
void main()
{
do
{
double t;//存储运算结果
char a[]="0";
char linshi[MAX];//计算临时用的
char biaodashi[MAX];//用来存放表达式
for(;;)//输入表达式
{
cout<<"请输入一个正确的数学表达式,形如7+8*6,按回车结束"<<endl;//输入表达式
cin>>biaodashi;
if(biaodashi[0]=='*'||biaodashi[0]=='/')
cout<<"您输入的表达式有误,请重新输入"<<endl;//判断首字符是否为*/,如果是提示错误别返回重新输入
else break;
}
if(biaodashi[0]=='-'||biaodashi[0]=='+')//判断首字符是否为+-,如果是给字符前加字符0
{
strcat(a,biaodashi);
strcpy(biaodashi,a);
}
strcpy(linshi,biaodashi);
t=compute(linshi);//计算
cout<<"运算结果是:"<<biaodashi<<'='<<t<<endl;//输出结果
}while(1);//计算完返回,接着输入
}
//自定义函数
double xsd(char x[])//判断是否有小数点,并把字符型转化为浮点型
{
for(int i=0;i<strlen(x);i++)
if(x[i]=='.')
{
double num = 0.0;
sscanf(x, "%f", &num);//
return num;
}
else
return atof(x);
}
double cmjia(char x[],char y[])//计算两字符加法,并返回double型值
{
double a,b;
a=xsd(x);
b=xsd(y);
double c=a+b;
return c;
}
double cmjian(char x[],char y[])//计算两字符减法,并返回double型值
{
double a,b;
a=xsd(x);
b=xsd(y);
double c=a-b;
return c;
}
double cmcheng(char x[],char y[])//计算两字符乘法,并返回double型值
{
double a,b;
a=xsd(x);
b=xsd(y);
double c=a*b;
return c;
}
double cmchu(char x[],char y[])//计算两字符除法,并返回double型值
{
double a,b;
a=xsd(x);
b=xsd(y);
double c=a/b;
return c;
}
double compute(char pstr[])//
{
char strleft[MAX],strright[MAX],coml[MAX],comr[MAX],com[MAX],ysf;//ysf代表运算符
double comzhi,zhi;//comzhi代表中途计算的值,com代表comzhi所对应的字符型
int n=0;
//先算乘除
for(int i=0;i<strlen(pstr);i++)
{
if(pstr[i]=='*'||pstr[i]=='/')
{
ysf=pstr[i];
// 获取左边要计算的数据coml和该数据前的字符串strleft
for(int j=i-1;j>=0;j--)
{
if(pstr[j]=='-'||pstr[j]=='+')
{
for(int k=0;k<=j;k++) strleft[k]=pstr[k];
strleft[k]='';//获取左边要计算数据前的字符串strleft
for(k=j+1,n=0;k<i;k++,n++) coml[n]=pstr[k];
coml[n]='';//获取左边要计算的数据coml
break;
}
else
for(int k=0;k<i;k++)
{
coml[k]=pstr[k];
strleft[0]='';
}
}
//获取右边要计算的数据comr和该数据后的字符串 strright
for(j=i+1;j<=strlen(pstr);j++)
{
if(pstr[j]=='-'||pstr[j]=='+'||pstr[j]=='*'||pstr[j]=='/'||pstr[j]=='')
{
for(int k=0,n=j;k<strlen(pstr)-i-1;k++,n++) strright[k]=pstr[n];
strright[k]='';
for(k=i+1,n=0;k<j;k++,n++) comr[n]=pstr[k];
comr[n]='';
break;
}
}
if(ysf=='*')//如果是*
{
comzhi=cmcheng(coml,comr);
sprintf(com,"%.3f",comzhi);
}
else//如果是/
{
comzhi=cmchu(coml,comr);
sprintf(com,"%.3f",comzhi);
}
strcat(strleft,com);//把数据前的字符和运算后的字符连接
strcat(strleft,strright);//把数据后的字符和运算后的字符连接
strcpy(pstr,strleft);//替换pstr
}//重新找操作符
}
//再找加减
for(i=0;i<strlen(pstr);i++)
{
if(pstr[i]=='+'||pstr[i]=='-')
{
ysf=pstr[i];
// 获取左边要计算的数据coml
for(int k=0;k<i;k++) coml[k]=pstr[k];
coml[k]='';
//获取右边要计算的数据和该数据后的字符串 strright
for(int j=i+1;j<=strlen(pstr);j++)
{
if(pstr[j]=='-'||pstr[j]=='+'||pstr[j]=='')
{
for(int k=0,n=j;k<=strlen(pstr)-j;k++,n++) strright[k]=pstr[n];
strright[k]='';
for(k=i+1,n=0;k<j;k++,n++) comr[n]=pstr[k];
comr[n]='';
break;
}
}
if(ysf=='+')
{
comzhi=cmjia(coml,comr);
sprintf(com,"%.3f",comzhi);
}
else
{
comzhi=cmjian(coml,comr);
sprintf(com,"%.3f",comzhi);
}
strcat(com,strright);
strcpy(pstr,com);
}
}
//没找到,说明全部+-都计算完了
zhi=atof(pstr);
return zhi;
测试代码
思考题:
比较以下二个工匠的做法,你认为哪种好?结合编码和单元测试,谈谈你的认识。
答:我觉着工匠二的方法更好,因为工匠二的方法对自己的训练很有帮助,他在没有外力的帮助下进行联系,然后再去修改,不断地提升自己。在最后的时候,即使在不使用水平线的时候,单独凭借
自己熟练的技巧,就可以将每一块砖保持水平。
实验小结
说实话这次实验的难度我觉着有点大,最主要的问题是cppunit的使用与安装上面,在编译cppunit的时候,我花费了很多时间
,还好最后在同学的帮助下,完成了这个实验。实验确实学习到了很多。给了我很大的帮助。