Wings-让单元测试智能全自动生成

前言

单元测试是保证软件质量非常有效的手段,无论是从测试理论早期介入测试的理念来看或是从单元测试不受UI影响可以高速批量验证的特性,所以业界所倡导的测试驱动开发,这个里面提到的测试驱动更多的就是指单元测试驱动。但一般开发团队还是很少的系统化的执行单元测试,针对应用软件的测试更多是由专业测试团队来执行黑盒测试。单元测试的最大的难点不在于无法确定输入输出,这毕竟是模块开发阶段就已经定好的,而在于单元测试用例的编写会耗费开发人员大量的工时,按照相关统计单元测试用例的时间甚至会远超过功能本身开发的时间。以下是几个最常见的开发不写单元测试的理由:

●需求总是无穷尽的,还有下阶段功能需求要实现,没空补单元

●要补的单元测试太多,无从下手,主观上抗拒。

●单元测试编写难度大。一方面原因可能是功能函数实现上不够合理,另一方面是没有(或者不知道)好用的单元测试框架和mock框架。

●单元测试不算入工作量内。

其次,功能需求还不稳定,写单元测试的性价比不高。换句话说,万一明天需求一变,那不光功能代码废了,单元测试也废了。如果不写单元测试,那这部分工夫就不会白费。

上述几点其实分析根本原因是单元测试编写太耗时,最终导致测试驱动的发动机失去了动力,致使测试驱动开发的美好愿景在现实场景熄火,因为构建这个驱动用的发动机实在是难度和成本太大了。 市场上的各种“x”Unit,单元测试框架仅仅解决了生成测试驱动的外框,没有任何基于深度程序理解的用例逻辑和数据的产生能力。因此在各种开发相关场景中都让开发人员产生抵触情绪。Wings的发布(目前针对C语言)则解决了这个困扰程序员的一个最大的难题,同时也有可能从根本上改变单元测试的现状,充分的、高效率的单元测试将有效缓解基于海量人力的系统级黑盒测试以及自动化测试的压力。

制约测试用例采用程序自动生成,最关键的底层技术是复杂的参数解析技术。即:能够在编译器层面对于任意复杂的类型,任意定义嵌套层级的递归解析。如果没有这个关键技术的突破,那么测试用例自动生成系统要么无法商用,要么将以极低的效率来演化、产生合规的测试数据。例如著名的模糊测试工具American Fuzzy Lop,它并不能够识别用户的程序所需要的结构类型,需要从最外层进行基于搜索算法的演化。程序的特性是接口层面的输入和内部某个模块的数据要求距离很远,外部数据通常是经过层层复杂转换才可以成为内部模块所需要的数据结构类型,因此从外层探索所需要的计算量和时间将是难以想象的。基于American Fuzzy Lop,为了能够生成一个合法的SQL 语句,让程序内部模块能够通过外围数据校验需要探索时间以天数计,远非分钟或者小时可以生成。另外一个制约性条件是:每个程序能够接手的输入都是经过精心结构编制、含有大量规则的数据,而这些数据通过随机+探索的方式生成是非常不现实和极其耗时的。所以,从黑盒以及最外层输入产生自动产生用例是不可行的。

如果从软件内部结构分析产生用例驱动,就需要对软件的编译结构进行深度理解。可行的测试用例生成系统,应该是基于程序的中间(关键入口)作为测试切入最为合适。这些模块的输入,已经将模糊的输入转化为高度结构化的参数。只要能够识别这些复杂结构,将复杂数据类型一步步降解为简单数据类型,同时完成参数构造,就可以自动完成驱动用例的生成。

基于模块的测试,可以划归为传统的单元测试,它是将缺陷发现并遏制在研发阶段最好的方法。但受限于单元测试需要开发大量的驱动程序,在行业内的推广和应用受到了极大的限制。当然单元测试也可以在系统集成完毕后执行,避免构建虚拟的桩程序。

星云测试日前全球首发的Wings产品,是一个智能的、全自动的单元测试用例生成系统,研究并解决了如下难点,现分享给大家。

(1)     程序参数深度分析问题

Wings通过编译器底层技术,将输入的源文件,按照函数为单位,形成模块对象。对象中包含函数的输入参数,返回值类型等信息,供驱动函数模块和测试用例模块使用。每个文件作为一个单元,针对其中的每个函数的每个参数进行深度解析,对于嵌套类型,复杂类型等都可以实现精确的解析和分解,将复杂类型逐层讲解为基础数据类型,并产生参数结构的描述文件(PSD)。

(2)     函数驱动自动生成模块

依据PSD文件的格式信息,自动生成被测源程序的所有驱动函数,单元测试过程不再依赖开发人员手动编写测试函数,只需将生成的驱动函数和被测源文件一起编译,即可执行测试并查看测试结果。测试驱动自动生成程序基于PSD描述,全自动构建驱动被测程序运行的所有参数,必须的全局变量,并可根据复杂变量的层级结构产生结构化的测试驱动程序,可以节省大量的单元测试用例的编写时间。

(3)     测试数据自动生成与管理

用于自动生成测试数据,测试数据与被测函数提取的信息相互对应,数据以一定的层次逻辑关系存储在json文件中。数据和经过分解和展开后的数据类型是一一对应的。这些数据用户可以根据业务要求随意边际,并且用json文件进行结构化,层次化展示,非常的清晰。其中的测试数据包括全局变量值、被测函数调用时的参数值。

Wings提供了一种自动生成驱动函数的单元测试方法,其中主要包含以下几个步骤:

图一:单元测试驱动生成流程

1     被测程序信息提取

通过对源程序的扫描提取出函数的结构信息,使用户不需要关心程序的结构信息,而被测程序的结构信息,主要包含程序中的全局变量以及函数信息,而函数信息主要包括函数的参数个数,参数类型以及返回值类型。而全局变量以及参数,最主要的提取出其中的符号信息,以及类型信息,针对一些复杂的类型,通过层层进行解析为基本数据类型,完成全局变量以及函数参数的构造。

变量的类型一般大致分为基本类型、构造类型、指针类型及空类型。Wings通过底层编译技术,针对不同的变量类型,进行不同的处理方式。

(1)基本类型,例如unsigned int u_int = 20等基本类型,Wings将解析出变量的名称为u_int,数据类型为unsigned int。

(2)     构造类型,构造类型大致分为数组,结构体,共用体,枚举类型。

● 数组类型,例如int array[2][3],数组名称为array,类型为int以及二维数组的长度,行为2,列为3。

●结构体类型,针对结构体为数组,结构体链表等,进行不同的标记划分。

(3)     指针类型,例如int **ptr = 0;,解析出指针为int类型的2级指针。

(4)     空类型,解析出类型为NULL。

(5)     系统类型,例如File、size_t等,标记为系统类型,不在对其往下进行分析,会添加到模板中,由用户进行赋值操作。

(6)     函数指针类型,分析出函数的返回值类型、参数类型以及参数个数

针对被测源程序的每个编译单元,将解析到的函数信息,保存在对应的PSD结构中,针对以下源代码实例进行说明:

 typedef struct my_structone
{
//基本类型
int i_int; //数组类型
int array_one[];
int array_two[][]; //指针类型
int *point_one;
int **point_two; //空类型
void *point; //位域类型
unsigned int w : ; //函数指针是指向函数的指针变量,即本质是一个指针变量
int(*functionPtr)(int, int); union
{
int a;
char b;
long long c;
}Dem; enum DAY
{
MON = , TUE, WED = , THU, FRI = , SAT, SUN
}dy;
}myy_structone;
typedef struct my_struct
{
//结构体包含结构体
myy_structone *structone; //结构体中包含系统头文件的类型
FILE file;
struct my_struct *next;
}myy_struct; //结构体作为函数参数 void StructTypeTest1(myy_struct m_struct); void StructTypeTest2(myy_struct *mm_struct); void StructTypeTest3(myy_struct mm_struct[]); void StructTypeTest4(myy_struct mm_struct[][]);

以上程序中,void StructTypeTest3(myy_struct mm_struct[2])保存的PSD结构如下:

 <StructTypeTest3 parmType0="myy_struct [2]" parmNum="1">
<mm_struct baseType1="ArrayType" RowSize="2" type="StructureOrClassType" name="my_struct">
<structone baseType1="PointerType" type="StructureOrClassType" name="my_structone">
<i_int baseType1="BuiltinType" type="ZOA_INT" />
<array_one baseType1="ArrayType" RowSize="2" type="ZOA_INT" />
<array_two baseType1="ArrayType" RowSize="3" baseType2="ArrayType" ColumnSize="4" type="ZOA_INT" />
<point_one baseType1="PointerType" type="ZOA_INT" />
<point_two baseType1="PointerType" baseType2="PointerType" type="ZOA_INT" />
<point baseType1="PointerType" type="ZOA_VOID" />
<w baseType1="BuiltinType" type="ZOA_UINT" bitfield="1" />
<functionPtr baseType1="FunctionPointType" type="ZOA_FUNC" returnType="int" parmType0="int" parmType1="int" parmNum="2" />
<Dem baseType1="UnionType" type="ZOA_UNION" name="NULL">
<a baseType1="BuiltinType" type="ZOA_INT" />
<b baseType1="BuiltinType" type="ZOA_CHAR_S" />
<c baseType1="BuiltinType" type="ZOA_LONGLONG" />
</Dem>
<dy baseType1="EnumType" type="ZOA_ENUM" name="DAY">
<MON type="ZOA_INT" value="1" />
<TUE type="ZOA_INT" value="2" />
<WED type="ZOA_INT" value="200" />
<THU type="ZOA_INT" value="201" />
<FRI type="ZOA_INT" value="100" />
<SAT type="ZOA_INT" value="101" />
<SUN type="ZOA_INT" value="102" />
</dy>
</structone>
<file baseType1="StructureOrClassType" type="StructureOrClassType" name="_iobuf" SystemVar="_iobuf" />
<next NodeType="LinkNode" baseType1="PointerType" type="StructureOrClassType" name="my_struct" />
</mm_struct>
<g_int globalType="globalVar" />
<returnType returnType="void" />
</StructTypeTest3>

其中PSD文件各节点代表的意义如下:

  • StructTypeTest3代表函数名,parmType0代表参数类型,parmNum代表参数个数
  • mm_struct代表函数参数的符号,baseType1代表类型的分类(基本数据类型、构造类型、指针类型、空类型),type代表具体的类型,包括int,char,short,long,double,float,bool,以及这些类型的unsigned类型等基础的类型,还有一些特殊的类型诸如:ZOA_FUN类型表示函数类型,StructureOrClassType表示结构体类型,等等,name代表结构体、联合体、枚举类型的名
  • i_int代表基本类型,基本类型作为最小的赋值单位
  • array_one代表数组类型,RowSize代表数组的长度,数组可以划分为一维数组,二维数组等
  • point代表指针类型,指针分为一级指针、二级指针等,一般指针当做函数参数作为数组使用,因此,针对基本类型的指针,采用动态分配数组的方式进行赋值,用户可依据需要,修改对应的值文件。
  • w代表位域类型,bitfileld代表所占位数
  • functionPtr代表函数指针类型,分别分析出参数类型、参数个数、返回值信息
  • Dem代表联合体类型
  • dy代表枚举类型,value代表枚举类型的取值
  • file代表结构体类型,SystemVar代表此变量属于系统头文件中的变量,针对此种类型的变量,Wings通过添加模板变量的方式,添加在模板库中,用户可依据具体需要进行特殊赋值。例如File类型的,处理方式为:
 /* 系统内置类型,特殊处理或者模板处理 */
char * fname = "E:/spacial.txt";
FILE * file = fopen(fname,"r");
_st.file = _file;

用户也可自行添加赋值方式。针对系统类型,Wings可以和普通用户自定义类型进行区分,当解析到系统内置类型的时候就可以停止向下进行递归分析。

  • g_int代表全局变量,globalType代表全局
  • next代表链表结构体,NodeType代表此结构为链表
  • returnType代表函数的返回值类型。

2     驱动程序的自动生成

在上文中,针对全局变量和函数的结构信息,进行了分析和提取,以下将利用提取到保存在PSD中的信息,完成被测源程序的驱动框架整体生成。

生成主要分为以下几个方面:

  • 全局变量的声明
  • 函数参数的赋值操作,针对函数参数的个数,依次赋值操作
  • 全局变量的赋值,针对分析得到函数使用的全局变量的个数,依次进行赋值操作
  • 原函数的调用

一些需要注意点如下:

  • l驱动生成过程中,针对一些特殊函数,例如main函数,static函数等,因为外部无法访问到,驱动生成暂时不做处理。
  • l针对每个被测源文件,生成对应的一个驱动文件。
  • l驱动控制包含在Driver_main.cpp中,可以通过宏自动配置函数的测试次数

由以上源程序,生成的驱动函数如下:

  • 所有变量的命名为在原变量的名称前,添加_
  • 通过获取生成对应的测试数据,对变量依次进行赋值操作
  • 针对系统内置参数,以及用户比较特殊的参数,通过模板方式统一配置赋值方式。
  • 对被测函数进行参数赋值与调用。

3     测试数据自动生成

测试用例的自动生成,利用提取到保存在PSD中的函数信息,进行测试用例数据的生成,以下是图三中PSD格式生成的一组数据,每组数据保存为JSON格式,更容易看到数据的层次关系。

    "StructTypeTest30" : {
"g_int" : ,
"mm_struct" : [
{
"file" : "NULL",
"next" : "NULL",
"structone" : {
"Dem" : {
"a" : ,
"b" : "A",
"c" :
},
"array_one" : [ , ],
"array_two" : [
[ , , , ],
[ , , , ],
[ , , , ]
],
"dy" : ,
"functionPtr" : "NULL",
"i_int" : ,
"point_one" : [ , , ],
"point_two" : [
[ , , ],
[ , , ],
[ , , ]
],
"w" :
}
},
{
"file" : "NULL",
"next" : "NULL",
"structone" : {
"Dem" : {
"a" : ,
"b" : "",
"c" :
},
"array_one" : [ , ],
"array_two" : [
[ , , , ],
[ , , , ],
[ , , , ]
],
"dy" : ,
"functionPtr" : "NULL",
"i_int" : ,
"point_one" : [ , , ],
"point_two" : [
[ , , ],
[ , , ],
[ , , ]
],
"w" :

针对每个编译单元,默认生成一组所有函数的对应的测试数据文件,值生成可以通过配置次数进行修改。

4     Mysql程序测试结果展示

如何完成驱动框架的生成,下面针对开源程序MySQL完整的生成过程,进行详细说明。

以下是Wings测试Mysql的主界面图:

点击文件按钮,设置被测源程序的工程目录。设置完成之后,点击功能操作,功能操作主要包括参数解析、驱动生成、值文件生成以及模板添加四个操作。分析对应生成以下几个文件夹:

其中,参数解析模块,对应生成FunXml以及GlobalXml,分别存放提取到的每个编译单元的函数信息及全局变量的信息。

驱动生成模块,会对应生成Wings_Projects文件夹,其中存放每个编译单元的驱动文件

值生成模块,存放每个编译单元的生成的测试数据。

下图为Mysql对应加载的驱动文件结构体信息,左侧导航树为生成的对应驱动文件,包含每个编译单元的函数以及函数的参数、全局变量的信息。点击其中某个编译单元,可以加载对应的驱动文件以及对应的值文件。

以上是Mysql的整体生成对应的驱动文件以及值文件,针对以下代码详细说明驱动文件。

  • 针对每个编译单元,全局变量的引用通过extern的方式。
  • 驱动函数,统一命名为Driver_XXX的方式,JSON作为获取测试数据的方式,times代表单函数的测试次数。
  • 针对每个参数的赋值操作,利用解析到的PSD存储格式,对每层结构依次进行赋值操作。

Wings的应用非常简单,下面是以在Visual Studio 2015中可正常编译的Mysql 代码为例,生成的测试数据的统计指标,整个生成过程无需任何人工介入,仅需要制定所需要生成驱动的源码的路径即可。

mysql测试数据

Mysql版本

5.5

C语言代码文件个数

578个

分析所用时间(PSD生成时间)

149.099s

驱动生成所用时间

27.461s

值生成所用时间

84.974s

电脑配置说明:

操作系统

Windows7

处理器

Inter(R) Core(TM) i7-7700cpu 3.60GHz

内存

8.00GB

系统类型

64位

以下是使用源码统计工具得到的结果,多达400多万行有效的单元测试代码是由Wings全自动生成的。更有意思的是:可以看到这些代码采用人工开发的成本高达1079个人月,成本更是达到了1079万之多。

Wings实现了由程序自动生成程序的第一步探索,目前发布的是第一版,有兴趣的开发者直接在码云平台(https://gitee.com/teststars/wings_release进行下载),商业授权提供了一个月无限功能体验期,可以快速体验Wings的神奇能力,Wings c语言版支持多平台,例如visual studio、vxworks、gcc、qt等。Wings由星云测试(www.teststar.cc)团队设计和研发,有兴趣的开发者可以通过码云的互动平台与星云测试团队取得联系,贡献自己的设计思路和产品使用反馈(凡被采纳的优秀建议,星云可以延长其免费使用期至少为三个月)。Wings具有强大的、底层的大幅度改进软件质量的基因,未来Wings的将深度优化自动编写的程序的可读性(更接近优秀程序员的编写水平)以及对于c++语言的支持。

<StructTypeTest3parmType0="myy_struct [2]"parmNum="1">

<mm_structbaseType1="ArrayType"RowSize="2"type="StructureOrClassType"name="my_struct">

<structonebaseType1="PointerType"type="StructureOrClassType"name="my_structone">

<i_intbaseType1="BuiltinType"type="ZOA_INT" />

<array_onebaseType1="ArrayType"RowSize="2"type="ZOA_INT" />

<array_twobaseType1="ArrayType"RowSize="3"baseType2="ArrayType"ColumnSize="4"type="ZOA_INT" />

<point_onebaseType1="PointerType"type="ZOA_INT" />

<point_twobaseType1="PointerType"baseType2="PointerType"type="ZOA_INT" />

<pointbaseType1="PointerType"type="ZOA_VOID" />

<wbaseType1="BuiltinType"type="ZOA_UINT"bitfield="1" />

<functionPtrbaseType1="FunctionPointType"type="ZOA_FUNC"returnType="int"parmType0="int"parmType1="int"parmNum="2" />

<DembaseType1="UnionType"type="ZOA_UNION"name="NULL">

<abaseType1="BuiltinType"type="ZOA_INT" />

<bbaseType1="BuiltinType"type="ZOA_CHAR_S" />

<cbaseType1="BuiltinType"type="ZOA_LONGLONG" />

</Dem>

<dybaseType1="EnumType"type="ZOA_ENUM"name="DAY">

<MONtype="ZOA_INT"value="1" />

<TUEtype="ZOA_INT"value="2" />

<WEDtype="ZOA_INT"value="200" />

<THUtype="ZOA_INT"value="201" />

<FRItype="ZOA_INT"value="100" />

<SATtype="ZOA_INT"value="101" />

<SUNtype="ZOA_INT"value="102" />

</dy>

</structone>

<filebaseType1="StructureOrClassType"type="StructureOrClassType"name="_iobuf"SystemVar="_iobuf" />

<nextNodeType="LinkNode"baseType1="PointerType"type="StructureOrClassType"name="my_struct" />

</mm_struct>

<g_intglobalType="globalVar" />

<returnTypereturnType="void" />

</StructTypeTest3>

【星云测试】Wings-让单元测试智能全自动生成的更多相关文章

  1. 【星云测试】Devops微服务架构下具有代码级穿透能力的精准测试

    微服务是Devops场景下热门的开发框架,在大型项目中被广泛采用.它把一个大型的单个应用程序和服务拆分为数十个的支持微服务,独立部署.互相隔离,通过扩展组件来处理功能瓶颈问题,比传统的应用程序更能有效 ...

  2. 【星云测试】开发者测试-采用精准测试工具对Spring Boot应用进行测试

    简介:本文主要介绍把现今主流的springboot框架项目和精准测试工具进行结合和应用,通过精准测试的数据穿透.数据采集.测试用例与代码的双向追溯.数据分析等一系列精准测试的特有功能,达到对项目质量的 ...

  3. 【星云测试】开发者测试(2)-采用精准测试工具对J2EE Guns开发框架进行测试

    配置测试Guns Guns简介 Guns是一个近几年来基于SpringBoot的开源便利且较新的JavaEE项目开发框架,它整合了springmvc + shiro + mybatis-plus + ...

  4. 【星云测试】开发者测试(3)-采用精准测试工具对springcloud微服务应用进行穿透测试

    1.微服务简介 微服务英文名称Microservice,Microservice架构模式就是将整个Web应用组织为一系列小的Web服务.这些小的Web服务可以独立地编译及部署,并通过各自暴露的API接 ...

  5. 【星云测试】开发者测试(4)-采用精准测试工具对dubbo微服务应用进行测试

    简介:本文主要目的是把现今主流的Dubbo框架项目和精准测试进行对接,通过精准测试的数据穿透.数据采集.测试用例与代码的双向追溯.数据分析等一系列精准测试的特有功能达到对项目质量的保证. 本次环境搭建 ...

  6. 星云测试插装编译流程与CI集成

    星云测试Horn插装采用脚本配置方式自动对语法进行扫描和插装,在整个插装过程中需要用到星云提供的插件工具.通过与CI集成,在CI编译前通过jenkins调用星云插装插件模块进行必要的数据填充,生成对应 ...

  7. JAVA 利用MyEclipse结合TestNG测试框架进行单元测试

    利用MyEclipse结合TestNG测试框架进行单元测试   by:授客 QQ:1033553122 测试环境 jdk1.8.0_121 myeclipse-10.0-offline-install ...

  8. Kail Linux渗透测试教程之免杀Payload生成工具Veil

    Kail Linux渗透测试教程之免杀Payload生成工具Veil 免杀Payload生成工具——Veil Kail Linux渗透测试教程之免杀Payload生成工具Veil,Veil是一款利用M ...

  9. 以太坊solidity智能合约-生成随机数

    Solidity随机数生成 在以太坊的只能合约中,没有提供像其他面向对象编程一样的生成随机数的工具类或方法.其实,所谓的随机数也是伪随机的,没有哪一种语言能够真正的生成随机数. 对于solidity来 ...

随机推荐

  1. 牛客Wannafly挑战赛11E 白兔的刁难

    传送门 如果大力推单位根反演就可以获得一个 \(k^2logn\) 的好方法 \[ans_{t}=\frac{1}{k}\sum_{i=0}^{k-1}(w_k^{-t})^i(w_k^i+1)^n\ ...

  2. 【SPOJ】MGLAR10 - Growing Strings

    Gene and Gina have a particular kind of farm. Instead of growing animals and vegetables, as it is us ...

  3. 【转载】MySQL数据库可以用任意ip连接访问的方法

    通过CMD命令行修改数据库表的一个字段的值,实现连接,访问. 第一步.找到MYSQL软件安装所在的bin目录: (1)cd\当前目录 (2)指定MYSQL安装的bin目录 (3)输入 -h local ...

  4. 初始react native遇到的问题

    转载自Andriod 使用react native时遇到的问题     打开现有项目报错: 从第一行Error可以知道是一个zip的压缩文件打不开,往下看应该是下载的Gradle文件有问题,提示也是让 ...

  5. 用WebStorm开发TypeScript

    为什么是TypeScript 最近在做H5的游戏,最终选定的TypeScript作为开发语言.主要是看重他有强类型和Class,作为习惯使用AS3,Java等强类型编程的人来说,还是习惯这种编程写法. ...

  6. 设置Web AppBuilder的HTTP代理

    在使用Web AppBuilder快速搭建webgis应用时,我们往往需要访问外部互联网的在线gis服务资源.假如要通过代理服务器才能访问互联网的在线gis服务资源,那么则需要预先配置web appb ...

  7. (C#) 多线程访问探讨,如果保证线程安全?

    先抛出几点疑问: 1. 多个线程同时访问同一个“值类型变量“(value type, stored in stack), 如果保证安全访问? 2. 多个线程同时访问同一个“引用类型变量“(refere ...

  8. 基于Apache Curator框架的ZooKeeper使用详解

    一 简介 Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封装的一套高级API 简化了ZooKeeper的操作.通过查看官方文档,可以发现Curator主要解决了三类问题 ...

  9. Jmeter与LoadRunner的异同

    1.jmeter的架构跟loadrunner原理一样,都是通过中间代理,监控&收集并发客户端发现的指令,把他们生成脚本,再发送到应用服务器,再监控服务器反馈的结果的一个过程. 2.分布式中间代 ...

  10. c# 设计模式 之:工厂模式之---简单工厂

    1.uml类图如下: 具体实现和依赖关系: 实现:SportCar.JeepCar.HatchbackCar 实现 Icar接口 依赖: Factory依赖 SportCar.JeepCar.Hatc ...