首页> 中国专利> 在单元测试中实现通用桩函数的装置及其实现方法

在单元测试中实现通用桩函数的装置及其实现方法

摘要

一种在单元测试中实现通用桩函数的装置及其实现方法,该装置包括:被测模块,用于存储被测函数;提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测函数的函数信息;符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息;缓冲区模块,用于存储实现桩函数所需的参数信息;通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数模块。该装置减少了单元测试过程中桩函数的编写工作量,而且实现难度小,能够在各种操作系统、编译环境下实现。

著录项

  • 公开/公告号CN101110055A

    专利类型发明专利

  • 公开/公告日2008-01-23

    原文格式PDF

  • 申请/专利权人 中兴通讯股份有限公司;

    申请/专利号CN200710121236.0

  • 发明设计人 马亮;马军;

    申请日2007-08-31

  • 分类号G06F11/36;

  • 代理机构北京律诚同业知识产权代理有限公司;

  • 代理人梁挥

  • 地址 518057 广东省深圳市南山区高新技术产业园科技南路中兴通讯大厦

  • 入库时间 2023-12-17 19:41:21

法律信息

  • 法律状态公告日

    法律状态信息

    法律状态

  • 2017-10-27

    未缴年费专利权终止 IPC(主分类):G06F11/36 授权公告日:20110713 终止日期:20160831 申请日:20070831

    专利权的终止

  • 2011-07-13

    授权

    授权

  • 2009-12-16

    实质审查的生效

    实质审查的生效

  • 2008-01-23

    公开

    公开

说明书

技术领域

本发明涉及软件测试技术,尤其涉及一种在单元测试中实现通用桩函数的装置及其实现方法。

背景技术

所谓桩函数就是指对某个存在或者不存在的函数的模拟,它在某些输入输出特性上与真实函数一致,但是函数内部逻辑简单。桩函数的价值在于函数的外部特性尽量和被替换的函数相似,而实现的工作量尽量减少,否则如果工作量很大,则不如直接使用真是的函数。

桩函数主要在以下场景中使用:

1.被测函数调用了一个未编写的函数,可以编写桩函数来代替被调用的函数;

2.桩函数也用于实现测试距离,由于被测函数的运行需要调用其他被调用函数(以下简称called函数),而这些called函数依赖于网络、数据库、硬件等复杂环境,所以为了使单元测试更容易进行,需要模拟这些called函数,隔离开复杂环境的依赖;

3.需要控制called函数的某些行为,如可以自由改变这些called函数的返回值、出参、全局变量,或者缩短called函数的执行时间。

只有采用自底向上的测试顺序才会不需要用到桩函数,但带来的问题也是严重的,比如需要所有的called函数都已实现,不能并行进行单元测试,不能对called函数进行控制等。在实践中,单元测试的有效策略是选取重要模块,尽早测试。这一策略决定了测试一个函数时由于需要隔离上层调用函数(以下简称calling函数)和下层called函数,calling函数用驱动函数来实现,called函数要用桩函数来代替,以实现对called函数的控制和隔离。

单元测试中桩函数的编写工作量是比较大的,每个被测函数所调用的函数都有可能要编写一个或者多个桩函数以便进行对called函数进行控制。但是每增加、减少或者改动一次桩函数都需要对源程序重新编译,影响测试效率。目前对这一问题基本上基于两个思路:1、手工用脚本编写,优点是不用重复编译,适用于被测代码变化少,而桩函数变动多的场景,缺点是编写工作量没有减少,而且使用者需要学习一种新的脚本语言,学习曲线长;2、自动生成桩函数,优点是减少了桩函数编写的工作量,缺点是增加了测试工具的复杂性,如果需要在各种编译环境下都能自动生成桩函数,对测试工具要求极高。

发明内容

本发明旨在解决现有技术中手工编写桩函数时工作量大、自动生成桩函数时对测试工具要求高的问题,提供了一种在单元测试中实现通用桩函数的装置及其实现方法,减少了单元测试过程中桩函数的编写工作量,而且实现难度小,能够在各种操作系统、编译环境下实现。

为了实现上述目的,本发明提供了一种在单元测试中实现通用桩函数的装置,其特征在于,包括:

被测模块,用于存储被测函数;

提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测函数的函数信息;

符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息;

缓冲区模块,用于存储实现桩函数所需的参数信息;

通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;

驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数模块。

上述的装置,其特征在于,所述缓冲区模块还用于存储所述桩函数执行后的参数信息。

上述的装置,其特征在于,

所述函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关系;

所述参数信息包括出参值、全局变量值和返回值。

为了更好地实现上述目的,本发明还提供了一种在单元测试中实现通用桩函数的实现方法,其特征在于,包括:

被测函数存储步骤,用于存储被测函数;

提取步骤,用于对存储的被测函数进行分析,获取所述被测函数的函数信息;

符号表存储步骤,用于存储所述获取的所述被测函数的函数信息;

缓冲区存储步骤,用于存储实现桩函数所需的参数信息;

通用桩函数实现步骤,用于获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;

驱动步骤,用于驱动所述被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数实现步骤。

上述的实现方法,其特征在于,所述缓冲区存储步骤还用于存储所述桩函数执行后的参数信息。

上述的实现方法,其特征在于,

所述函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关系;

所述参数信息包括出参值、全局变量值和返回值。

上述的实现方法,其特征在于,所述驱动步骤进一步包括:

F1,根据参数桩函数索引,获取桩函数名称;

F2,获取所述桩函数的出参值、全局变量值和返回值;

F3,设置被调用函数的出参值、全局变量值和返回值;

F4,正确调整堆栈指针,返回控制权给调用函数。

上述的实现方法,其特征在于,所述F1之后进一步包括F11,调用UserStub函数,执行用户自定义代码。

为了更好地实现上述目的,本发明还提供了一种实现上述方法的测试系统,包括一种在单元测试中实现通用桩函数的装置,其特征在于,所述装置包括:

被测模块,用于存储被测函数;

提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测函数的函数信息;

符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息;

缓冲区模块,用于存储实现桩函数所需的参数信息;

通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;

驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数模块。

上述的系统,其特征在于,所述缓冲区模块还用于存储所述桩函数执行后的参数信息。

本发明提供的装置及其实现方法,通过对源程序的扫描提取结构信息,使用户不需要关心程序的结构,同时用户只需要提供桩函数所需要的输入输出数据,其他都由桩函数处理,有利于实现数据驱动的单元测试,可复用性和灵活性大大提高,并且被测程序一次编译后,可灵活创建多个测试用例,减少了系统的编译次数,被测程序可以在目标机上,也可以在PC机上,能够灵活适应多种测试环境。

附图说明

图1是本发明中测试系统结构框图;

图2是本发明中在单元测试中实现通用桩函数的装置的结构框图;

图3是本发明中实现方法流程图;

图4是本发明具体实施例中跳转指定被调用函数流程图;

图5是本发明具体实施例中通用桩函数执行流程图。

具体实施方式

图1所示为本发明中测试系统结构框图,系统100包括提取模块110、符号表模块120、驱动函数模块130、被测模块140、通用桩函数模块150、缓冲区模块160和用户端170,其中用户通过用户端170将测试用例(包含called函数执行后的参数、全局变量、返回值)输入缓冲区模块160,通用桩函数模块150是本发明的主要部分,通过与缓冲区模块160进行交互,获得桩函数的出参值、全局变量值以及返回值,并设置对应的堆栈以及寄存器;缓冲区模块160是用于存储桩函数的出参值、全局变量值和返回值的缓冲区,以及访问该缓冲区的应用程序接口(API),该缓冲区模块结构中主要包括了如下字段:Index-参数的索引,VarName-全局变量的名称,size-所占字节的大小,pointerflag-是否为指针(数组和指针同样处理),data-存放数值的数据块;提取模块110以被测模块140为输入,通过对被测模块140进行分析(如对被测模块140源文件进行静态分析,但本发明也可以适用于其他形式的分析过程),获取被测模块140的数据结构、函数参数、返回值、全局变量、函数调用关系等信息,并通过符号表模块120存储这些信息,这些信息是通用桩函数模块150运行的基础;符号表模块120封装了提取模块110获取的信息,并通过对外接口,以供其他模块获取被测函数和called函数的参数、返回值、全局变量的类型、大小等信息;被测模块140存储被测试的函数,在编译之前是包含了一组被测函数的源文件,在编译之后是一组由被测函数源文件生成的二进制代码;驱动函数模块130用于存储驱动函数,通过该驱动函数运行被测函数。本发明中在单元测试中实现通用桩函数的装置200包括提取模块110、符号表模块120、驱动函数模块130、被测模块140、通用桩函数模块150和缓冲区模块160,如图2所示。

图3所示为利用图2所示的装置再单元测试中实现通用桩函数的流程图,包括:

步骤S310,在被测函数模块中存储被测函数;

步骤S320,提取模块对存储的被测函数进行分析,获取所述被测函数的函数信息,该函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关系;

步骤S330,在符号表模块中存储获取的所述被测函数的函数信息;

步骤S340,在缓冲区模块中存储实现桩函数所需的参数信息,包括出参值、全局变量值和返回值;

步骤S350,通用桩函数模块从缓冲区模块中获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;

步骤S360,驱动驱动所述被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数实现步骤,具体包括:

F1,根据参数桩函数索引,获取桩函数名称;

F2,调用UserStub函数,执行用户自定义代码

F3,获取所述桩函数的出参值、全局变量值和返回值;

F4,设置被调用函数的出参值、全局变量值和返回值;

F5,正确调整堆栈指针,返回控制权给调用函数。

图4是本发明实施例中跳转指定被调用函数流程图,将指定被调用函数跳转到通用桩函数,也就是对被调用函数打桩,包括如下步骤:

步骤S410,从函数名称获取called函数的地址OldAddr和通用桩函数的地址NewAddr,根据不同的操作系统、不同语言、不同编译器实现该步骤有不同的方法,有些操作系统如vxworks本身就提供这样的API,有些操作系统如windows不直接提供API,但是可以通过分析编译器生成.exe或者.map文件来获得函数地址;

步骤S420,获取called函数的索引index,在通用桩函数中,只有一个桩函数对应了所有的called函数,需要在通用桩函数中区分该次桩函数是对哪个called函数打桩,本发明中采用了函数索引表的机制,建立函数索引表是为了提供两个API:根据函数索引获取函数名和根据函数名获取函数索引,函数索引表的建立方法很多,可以分析源文件,也可以通过分析编译器生成.exe或者.map文件来建立,只要能够获取当前被测模块中所有函数的列表,都可以建立函数索引表,本发明中根据函数名称来获取函数索引index;

步骤S430,计算指定called函数和通用桩函数地址偏移量offset,该偏移量用于从called函数的入口跳转到通用桩函数的入口,offset的计算方法如下:

offset=(DWORD32)((DWORD32)newAddr-((DWORD32)oldAddr+CODE_LEN))

这里的CODE_LEN是要替换的指令的长度,由于jmp指令位于要替换的指令的最后,这样在计算相对偏移offset时需要考虑CODE_LEN,本发明中需要替换的指令为push和jmp,在32位系统里,一条push指令的长度是2个字节,一条jmp指令的长度是5个字节,所以这里定义CODE_LEN为7;

步骤S440,以push index和jmp offset两条指令替换指定called函数的入口处指令,push index指令将函数索引压入函数堆栈中,这样在跳转至通用桩函数的入口后,取出堆栈中存储的该索引值,即可得知该次执行中,通用桩函数对应的called函数,以从缓冲区中获取预置的该called函数的出参、全局变量以及返回值,以完成桩函数的作用,其中jmp offset指令用于使called函数的入口从原入口强制跳转至通用桩函数的入口,而且函数堆栈则保持called函数的堆栈状态,替换的伪代码如下:

char cCode[10];//用于缓存替换指令。

cCode[0]=(char)0x6a;//push指令机器码

*((DWORD32*)&cCode[1])=index;//index是步骤202中获得的

cCode[2]=(char)0xe9; //jmp指令机器码

*((DWORD32*)&cCode[3])=offset;//offset是步骤203中获得的

#ifdef WIN32

VirtualProtect(oldAddr,CODE_LEN,PAGE_EXECUTE_READWRITE,&dw);

//oldAddr是指定called函数的入口地址,VirtualProtect用于把指定代码段变为可写,这只在windows下需要,因为windows下代码段缺省不可写,在vxworks、Linux等操作系统下不需要作这样的处理,代码段缺省是可写的。

#endif

memcpy(oldAddr,cCode,CODE_LEN);//覆盖called函数入口的前7个字节为push index;jmp offset对应的机器码。

每次被测函数调用指定called函数时,由于指定called函数的入口处指令已被替换为push index、jmp offset两条指令,就会直接跳转到通用桩函数的入口地址。

图5所示为本发明实施例中通用桩函数执行流程图,包括:

步骤S510,调整堆栈,并根据函数索引获取函数名,本发明由于使用了打桩技术,直接从called函数入口通过jmp offset指令跳转到通用桩函数入口代码,在进入通用桩函数入口后,首先保存寄存器ebp,并在寄存器ebp内保存栈顶指针esp的值,然后获取栈顶存储的函数索引,并根据函数索引表获取called函数名,伪代码如下:

int*oldesp;

int*newesp;

int stubid;

#ifdef_M_IX86//X86 Only!

    _asm  {

mov oldesp,esp

add esp,4//跳过堆栈中的index,这个index是步骤240中pushindex指令压入堆栈的。

mov newesp,esp//newesp用于访问called函数的堆栈,此时newesp指向called函数堆栈的第一个参数地址(以单字节对齐、_cdecl调用方式为例,是最左边的参数)。

}

stubid=*oldesp;//取出栈顶的函数索引。

_asm{

    push ebp

    mov ebp,esp

    sub esp,_LOCAL_SIZE

}//保存called函数堆栈的ebp和esp,并建立通用桩函数的本地变量堆栈。

#endif

    ATE_GetFuncNameByIndex(stubid,funcname);//从函数索引获取函数名称。

步骤S520,调用UserStub,执行用户自定义代码,该步骤为可选步骤,本发明中提供了UserStub函数,用户可在该函数中写入自定义的代码,以设置called函数的缓冲区,达到修改called函数出参和全局变量的目的;

步骤S530,查找缓冲区,获取called函数的出参值、全局变量值和返回值,该步骤中,根据步骤S510中获取的called函数名,查找缓冲区获取预设的called函数的出参值、全局变量值和返回值,其中缓冲区提供了下列API以进行对缓冲区的设置和读取:

读取缓冲区中的函数参数值;

设置缓冲区中的函数参数值;

读取缓冲区中的全局变量值;

设置缓冲区中的全局变量值;

步骤S540,设置called函数出参值、全局变量值和返回值,由于使用了打桩技术,替换了called函数入口地址的指令,所以直接从called函数入口通过jmp offset指令跳转到通用桩函数入口,因此此时堆栈中应该保留了called函数的堆栈,需要通过一定的计算来取得堆栈中的called函数的参数地址。

全局变量和函数一样作为全局符号,在编译时分配有地址(获取地址方法同步骤S410),从缓冲区中读取其预设值,设置该全局变量的值为预设值,设置called函数的出参需要访问该函数的堆栈,以设置其值,访问堆栈通过栈顶指针newesp访问,按照called函数的调用约定顺序反向计算参数在堆栈中的地址,该步骤的具体细节与编译器紧密相关,随字节对齐和函数调用预定不同而变化,下面以单字节对齐、_cdecl调用方式为例详细说明:

1、对于called函数的n个参数,从第一个参数到第n个参数,依次做如下操作:

(1)获取该参数的缓冲区parabuffer,获取该参数所占字节数size;

(2)如果参数大小不是4的整数倍,则补齐至4的整数倍:size=size+(4-size%4);

(3)如果该参数为指针,则代表此参数可以作为出参,可以修改此参数的值,则以parabuffer的data数据块替换该参数指向的数据块,伪代码是:memcpy(*newesp,parabuffer->data,size);//*newesp代表该参数指向的内存地址;

(4)如果该参数不是指针,则代表此参数不可以作为出参,不需要改变其值;

(5)执行指令add newesp,size,执行后newesp指向堆栈中的下一个参数的地址;

2、对于被测函数中的每个全局变量,依次作如下操作:

(1)获取该全局变量的缓冲区,记为pGlovar;

(2)获取该全局变量的地址,记为GlovarAdd;

(3)如果该全局变量为指针,则将缓冲区数据块的地址拷贝至GlovarAdd中,否则将缓冲区数据块的值拷贝至GlovarAdd中.

3、设置called函数返回值,called函数的返回值存放在寄存器eax中,设置方式如下:

(1)如果called函数的返回值为指针,则将返回值的地址放入寄存器eax中;

(2)如果called函数的返回值所占字节数大于4字节,将返回值的地址放入寄存器eax中;

(3)否则,将返回值的值直接放入寄存器eax中。

步骤S550,调整函数堆栈,返回控制权给调用者,返回函数流程至called函数的调用者,继续执行后续代码,在进入通用桩函数时,保存寄存器ebp,并在寄存器ebp内保存栈顶指针esp的值,在退出通用桩函数时,esp的值还原为保存在ebp内的值,并恢复寄存器ebp,这样就回复了函数的堆栈,该步骤的伪代码如下:

#ifdef_M_IX86//X86 Only!

    _asm{

        mov esp,ebp

        pop ebp

        ret

    }

#endif

上述示例均以INTEL处理器和C语言为例,并不表示对本发明应用范围的限制。

当然,本发明还可有其它多种实施例,在不背离本发明精神及其实质的情况下,熟悉本领域的普通技术人员当可根据本发明做出各种相应的改变和变形,但这些相应的改变和变形都应属于本发明所附的权利要求的保护范围。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号