单元测试是保证软件质量很是有效的手段,不管是从测试理论早期介入测试的理念来看或是从单元测试不受UI影响能够高速批量验证的特性,因此业界所倡导的测试驱动开发,这个里面提到的测试驱动更多的就是指单元测试驱动。但通常开发团队仍是不多的系统化的执行单元测试,针对应用软件的测试更可能是由专业测试团队来执行黑盒测试。单元测试的最大的难点不在于没法肯定输入输出,这毕竟是模块开发阶段就已经定好的,而在于单元测试用例的编写会耗费开发人员大量的工时,按照相关统计单元测试用例的时间甚至会远超过功能自己开发的时间。如下是几个最多见的开发不写单元测试的理由:mysql
●需求老是无穷尽的,还有下阶段功能需求要实现,没空补单元c++
●要补的单元测试太多,无从下手,主观上抗拒。git
●单元测试编写难度大。一方面缘由多是功能函数实现上不够合理,另外一方面是没有(或者不知道)好用的单元测试框架和mock框架。程序员
●单元测试不算入工做量内。算法
其次,功能需求还不稳定,写单元测试的性价比不高。换句话说,万一明天需求一变,那不光功能代码废了,单元测试也废了。若是不写单元测试,那这部分工夫就不会白费。sql
上述几点其实分析根本缘由是单元测试编写太耗时,最终致使测试驱动的发动机失去了动力,导致测试驱动开发的美好愿景在现实场景熄火,由于构建这个驱动用的发动机实在是难度和成本太大了。 市场上的各类“x”Unit,单元测试框架仅仅解决了生成测试驱动的外框,没有任何基于深度程序理解的用例逻辑和数据的产生能力。所以在各类开发相关场景中都让开发人员产生抵触情绪。Wings的发布(目前针对C语言)则解决了这个困扰程序员的一个最大的难题,同时也有可能从根本上改变单元测试的现状,充分的、高效率的单元测试将有效缓解基于海量人力的系统级黑盒测试以及自动化测试的压力。json
制约测试用例采用程序自动生成,最关键的底层技术是复杂的参数解析技术。即:可以在编译器层面对于任意复杂的类型,任意定义嵌套层级的递归解析。若是没有这个关键技术的突破,那么测试用例自动生成系统要么没法商用,要么将以极低的效率来演化、产生合规的测试数据。例如著名的模糊测试工具American Fuzzy Lop,它并不可以识别用户的程序所须要的结构类型,须要从最外层进行基于搜索算法的演化。程序的特性是接口层面的输入和内部某个模块的数据要求距离很远,外部数据一般是通过层层复杂转换才能够成为内部模块所须要的数据结构类型,所以从外层探索所须要的计算量和时间将是不可思议的。基于American Fuzzy Lop,为了可以生成一个合法的SQL 语句,让程序内部模块可以经过外围数据校验须要探索时间以天数计,远非分钟或者小时能够生成。另一个制约性条件是:每一个程序可以接手的输入都是通过精心结构编制、含有大量规则的数据,而这些数据经过随机+探索的方式生成是很是不现实和极其耗时的。因此,从黑盒以及最外层输入产生自动产生用例是不可行的。数组
若是从软件内部结构分析产生用例驱动,就须要对软件的编译结构进行深度理解。可行的测试用例生成系统,应该是基于程序的中间(关键入口)做为测试切入最为合适。这些模块的输入,已经将模糊的输入转化为高度结构化的参数。只要可以识别这些复杂结构,将复杂数据类型一步步降解为简单数据类型,同时完成参数构造,就能够自动完成驱动用例的生成。数据结构
基于模块的测试,能够划归为传统的单元测试,它是将缺陷发现并遏制在研发阶段最好的方法。但受限于单元测试须要开发大量的驱动程序,在行业内的推广和应用受到了极大的限制。固然单元测试也能够在系统集成完毕后执行,避免构建虚拟的桩程序。框架
星云测试日前全球首发的Wings产品,是一个智能的、全自动的单元测试用例生成系统,研究并解决了以下难点,现分享给你们。
Wings经过编译器底层技术,将输入的源文件,按照函数为单位,造成模块对象。对象中包含函数的输入参数,返回值类型等信息,供驱动函数模块和测试用例模块使用。每一个文件做为一个单元,针对其中的每一个函数的每一个参数进行深度解析,对于嵌套类型,复杂类型等均可以实现精确的解析和分解,将复杂类型逐层讲解为基础数据类型,并产生参数结构的描述文件(PSD)。
依据PSD文件的格式信息,自动生成被测源程序的全部驱动函数,单元测试过程再也不依赖开发人员手动编写测试函数,只需将生成的驱动函数和被测源文件一块儿编译,便可执行测试并查看测试结果。测试驱动自动生成程序基于PSD描述,全自动构建驱动被测程序运行的全部参数,必须的全局变量,并可根据复杂变量的层级结构产生结构化的测试驱动程序,能够节省大量的单元测试用例的编写时间。
用于自动生成测试数据,测试数据与被测函数提取的信息相互对应,数据以必定的层次逻辑关系存储在json文件中。数据和通过分解和展开后的数据类型是一一对应的。这些数据用户能够根据业务要求随意边际,而且用json文件进行结构化,层次化展现,很是的清晰。其中的测试数据包括全局变量值、被测函数调用时的参数值。
Wings提供了一种自动生成驱动函数的单元测试方法,其中主要包含如下几个步骤:
图一:单元测试驱动生成流程
经过对源程序的扫描提取出函数的结构信息,使用户不须要关心程序的结构信息,而被测程序的结构信息,主要包含程序中的全局变量以及函数信息,而函数信息主要包括函数的参数个数,参数类型以及返回值类型。而全局变量以及参数,最主要的提取出其中的符号信息,以及类型信息,针对一些复杂的类型,经过层层进行解析为基本数据类型,完成全局变量以及函数参数的构造。
变量的类型通常大体分为基本类型、构造类型、指针类型及空类型。Wings经过底层编译技术,针对不一样的变量类型,进行不一样的处理方式。
(1)基本类型,例如unsigned int u_int = 20等基本类型,Wings将解析出变量的名称为u_int,数据类型为unsigned int。
针对被测源程序的每一个编译单元,将解析到的函数信息,保存在对应的PSD结构中,针对如下源代码实例进行说明:
typedef struct my_structone { //基本类型 int i_int; //数组类型 int array_one[2]; int array_two[3][4]; //指针类型 int *point_one; int **point_two; //空类型 void *point; //位域类型 unsigned int w : 1; //函数指针是指向函数的指针变量,即本质是一个指针变量 int(*functionPtr)(int, int); union { int a; char b; long long c; }Dem; enum DAY { MON = 1, TUE, WED = 200, THU, FRI = 100, 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[2]); void StructTypeTest4(myy_struct mm_struct[2][3]);
以上程序中,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文件各节点表明的意义以下:
/* 系统内置类型,特殊处理或者模板处理 */ char * fname = "E:/spacial.txt"; FILE * file = fopen(fname,"r"); _st.file = _file;
用户也可自行添加赋值方式。针对系统类型,Wings能够和普通用户自定义类型进行区分,当解析到系统内置类型的时候就能够中止向下进行递归分析。
在上文中,针对全局变量和函数的结构信息,进行了分析和提取,如下将利用提取到保存在PSD中的信息,完成被测源程序的驱动框架总体生成。
生成主要分为如下几个方面:
一些须要注意点以下:
由以上源程序,生成的驱动函数以下:
测试用例的自动生成,利用提取到保存在PSD中的函数信息,进行测试用例数据的生成,如下是图三中PSD格式生成的一组数据,每组数据保存为JSON格式,更容易看到数据的层次关系。
"StructTypeTest30" : { "g_int" : 11624, "mm_struct" : [ { "file" : "NULL", "next" : "NULL", "structone" : { "Dem" : { "a" : 20888, "b" : "A", "c" : 19456 }, "array_one" : [ 24441, 12872 ], "array_two" : [ [ 18675, 30300, 32216, 19566 ], [ 13566, 13319, 11179, 18867 ], [ 30514, 21664, 21641, 28262 ] ], "dy" : 101, "functionPtr" : "NULL", "i_int" : 18271, "point_one" : [ 28024, 32245, 2129 ], "point_two" : [ [ 18165, 32335, 6429 ], [ 30225, 18252, 2764 ], [ 3177, 3622, 29789 ] ], "w" : 16862 } }, { "file" : "NULL", "next" : "NULL", "structone" : { "Dem" : { "a" : 2651, "b" : "7", "c" : 12159 }, "array_one" : [ 1274, 24318 ], "array_two" : [ [ 27944, 1208, 29647, 20840 ], [ 4972, 27297, 17456, 13614 ], [ 22441, 1160, 8940, 29420 ] ], "dy" : 200, "functionPtr" : "NULL", "i_int" : 15434, "point_one" : [ 29394, 3868, 25406 ], "point_two" : [ [ 13575, 14736, 20728 ], [ 9132, 2297, 2113 ], [ 26252, 14896, 10985 ] ], "w" : 12354
针对每一个编译单元,默认生成一组全部函数的对应的测试数据文件,值生成能够经过配置次数进行修改。
如何完成驱动框架的生成,下面针对开源程序MySQL完整的生成过程,进行详细说明。
如下是Wings测试Mysql的主界面图:
点击文件按钮,设置被测源程序的工程目录。设置完成以后,点击功能操做,功能操做主要包括参数解析、驱动生成、值文件生成以及模板添加四个操做。分析对应生成如下几个文件夹:
其中,参数解析模块,对应生成FunXml以及GlobalXml,分别存放提取到的每一个编译单元的函数信息及全局变量的信息。
驱动生成模块,会对应生成Wings_Projects文件夹,其中存放每一个编译单元的驱动文件
值生成模块,存放每一个编译单元的生成的测试数据。
下图为Mysql对应加载的驱动文件结构体信息,左侧导航树为生成的对应驱动文件,包含每一个编译单元的函数以及函数的参数、全局变量的信息。点击其中某个编译单元,能够加载对应的驱动文件以及对应的值文件。
以上是Mysql的总体生成对应的驱动文件以及值文件,针对如下代码详细说明驱动文件。
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++语言的支持。