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

文档序号:6610254阅读:290来源:国知局
专利名称:在单元测试中实现通用桩函数的装置及其实现方法
技术领域
本发明涉及软件测试技术,尤其涉及一种在单元测试中实现通用桩函数的 装置及其实现方法。
背景技术
所谓桩函数就是指对某个存在或者不存在的函数的模拟,它在某些输入输 出特性上与真实函数一致,但是函数内部逻辑简单。桩函数的价值在于函数的 外部特性尽量和被替换的函数相似,而实现的工作量尽量减少,否则如果工作 量很大,则不如直接使用真是的函数。
桩函数主要在以下场景中使用
1. 被测函数调用了一个未编写的函数,可以编写桩函数来代替被调用的 函数;
2. 桩函数也用于实现测试距离,由于被测函数的运行需要调用其他被调 用函数(以下简称called函数),而这些called函数依赖于网络、数据库、硬 件等复杂环境,所以为了使单元测试更容易进行,需要模拟这些called函数, 隔离开复杂环境的依赖;
3. 需要控制called函数的某些行为,如可以自由改变这些called函数的 返回值、出参、全局变量,或者縮短called函数的执行时间。
只有采用自底向上的测试顺序才会不需要用到桩函数,但带来的问题也是 严重的,比如需要所有的called函数都已实现,不能并行进行单元测试,不能 对called函数进行控制等。在实践中,单元测试的有效策略是选取重要模块, 尽早测试。这一策略决定了测试一个函数时由于需要隔离上层调用函数(以下 简称calling函数)和下层called函数,calling函数用驱动函数来实现,called 函数要用桩函数来代替,以实现对called函数的控制和隔离。
单元测试中桩函数的编写工作量是比较大的,每个被测函数所调用的函数 都有可能要编写一个或者多个桩函数以便进行对called函数进行控制。但是每
增加、减少或者改动一次桩函数都需要对源程序重新编译,影响测试效率。目 前对这一问题基本上基于两个思路1、手工用脚本编写,优点是不用重复编 译,适用于被测代码变化少,而桩函数变动多的场景,缺点是编写工作量没有 减少,而且使用者需要学习一种新的脚本语言,学习曲线长;2、自动生成桩 函数,优点是减少了桩函数编写的工作量,缺点是增加了测试工具的复杂性, 如果需要在各种编译环境下都能自动生成桩函数,对测试工具要求极高。

发明内容
本发明旨在解决现有技术中手工编写桩函数时工作量大、自动生成桩函数 时对测试工具要求高的问题,提供了一种在单元测试中实现通用桩函数的装置 及其实现方法,减少了单元测试过程中桩函数的编写工作量,而且实现难度小, 能够在各种操作系统、编译环境下实现。
为了实现上述目的,本发明提供了一种在单元测试中实现通用桩函数的装 置,其特征在于,包括
被测模块,用于存储被测函数;
提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测
函数的函数信息;
符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息; 缓冲区模块,用于存储实现桩函数所需的参数信息;
通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信 息,设置对应的堆栈以及寄存器,并实现桩函数;
驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数 在调用被调用函数时跳转到所述通用桩函数模块。
上述的装置,其特征在于,所述缓冲区模块还用于存储所述桩函数执行后 的参数信息。
上述的装置,其特征在于,
所述函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关
系;
所述参数信息包括出参值、全局变量值和返回值。
为了更好地实现上述目的,本发明还提供了一种在单元测试中实现通用桩
函数的实现方法,其特征在于,包括
被测函数存储步骤,用于存储被测函数;
提取步骤,用于对存储的被测函数进行分析,获取所述被测函数的函数信
息;
符号表存储步骤,用于存储所述获取的所述被测函数的函数信息; 缓冲区存储步骤,用于存储实现桩函数所需的参数信息;
通用桩函数实现步骤,用于获取实现桩函数所需的参数信息,设置对应的
堆栈以及寄存器,并实现桩函数;
驱动步骤,用于驱动所述被测函数,并使所述被测函数在调用被调用函数 时跳转到所述通用桩函数实现步骤。
上述的实现方法,其特征在于,所述缓冲区存储步骤还用于存储所述桩函 数执行后的参数信息。
上述的实现方法,其特征在于,
所述函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关
系;
所述参数信息包括出参值、全局变量值和返回值。 上述的实现方法,其特征在于,所述驱动步骤进一步包括 Fl,根据参数桩函数索引,获取桩函数名称; F2,获取所述桩函数的出参值、全局变量值和返回值; F3,设置被调用函数的出参值、全局变量值和返回值; F4,正确调整堆栈指针,返回控制权给调用函数。
上述的实现方法,其特征在于,所述Fl之后进一步包括Fl 1 ,调用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,驱动驱动所述被测函数,并使所述被测函数在调用被调用函 数时跳转到所述通用桩函数实现步骤,具体包括
Fl,根据参数桩函数索引,获取桩函数名称;
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)oldAddrfCODE— 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 (:0^[10];//用于缓存替换指令。
cCode
=(char)0x6a; 〃push指令机器码
*((DWORD32*)&cCode[l])= index;〃index是步骤202中获得的 cCode[2] = (char)0xe9;〃jmp指令机器码 *((DWORD32*)&cCode[3]) = offset; 〃offset是步骤203中获得的 #ifdef WIN32
VirtualProtect(oldAddr,CODE—LEN,PAGE—EXECUTE一READWRITE,&dw
);
〃oWAddr是指定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* oldespj
int承newespj
int stubidj
#ifdef _M—1X86 〃 X86 Only! —asm {
mov oldesp,esp
add esp,4 〃跳过堆桟中的index,这个index是步骤240中push index指令压入堆栈的。
mov newesp,esp 〃newesp用于访问called函数的堆栈,此时 newesp指向called函数堆栈的第一个参数地址(以单字节对齐、—cdecl调用方 式为例,是最左边的参数)。
stubid=* 0ldesp;〃取出栈顶的函数索弓l。 —asm {
push ebp mov ebp, esp sub esp, —LOCAL—SIZE } 〃保存called函数堆栈的ebp和esp,并建立通用桩函数的本地变量堆栈。
#endif
ATE—GetFuncN画Bylndex(stubid,ftmcname);〃从函数索引获取函数名称。
步骤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) 获取该参数的缓冲区pambuffer,获取该参数所占字节数size;
(2) 如果参数大小不是4的整数倍,则补齐至4的整数倍size= size+(4-size%4);
(3) 如果该参数为指针,则代表此参数可以作为出参,可以修改此参数的 值,则以pambuffer的data数据块替换该参数指向的数据块,伪代码是 memcpy(*newesp,parabuffer->data,size);〃*newesp代表该参数指向的内存地址;
(4) 如果该参数不是指针,则代表此参数不可以作为出参,不需要改变其
值;
(5) 执行指令addnewesp,size,执行后newesp指向堆栈中的下一个参数的 地址;
2、 对于被测函数中的每个全局变量,依次作如下操作
(1) 获取该全局变量的缓冲区,记为pGlovar;
(2) 获取该全局变量的地址,记为GlovarAdd;
(3) 如果该全局变量为指针,则将缓冲区数据块的地址拷贝至GlovarAdd 中,否则将缓冲区数据块的值拷贝至GlovarAdd中.
3、 设置called函数返回值,called函数的返回值存放在寄存器eax中,设 置方式如下
(1) 如果called函数的返回值为指针,则将返回值的地址放入寄存器eax
中;
(2) 如果called函数的返回值所占字节数大于4字节,将返回值的地址放入
寄存器eax中;
P)否则,将返回值的值直接放入寄存器eax中。
步骤S550,调整函数堆栈,返回控制权给调用者,返回函数流程至called 函数的调用者,继续执行后续代码,在进入通用桩函数时,保存寄存器ebp, 并在寄存器ebp内保存栈顶指针esp的值,在退出通用桩函数时,esp的值还 原为保存在ebp内的值,并恢复寄存器ebp,这样就回复了函数的堆栈,该步
骤的伪代码如下
<formula>formula see original document page 13</formula>上述示例均以INTEL处理器和C语言为例,并不表示对本发明应用范围 的限制。
当然,本发明还可有其它多种实施例,在不背离本发明精神及其实质的情 况下,熟悉本领域的普通技术人员当可根据本发明做出各种相应的改变和变 形,但这些相应的改变和变形都应属于本发明所附的权利要求的保护范围。
权利要求
1、一种在单元测试中实现通用桩函数的装置,其特征在于,包括被测模块,用于存储被测函数;提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测函数的函数信息;符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息;缓冲区模块,用于存储实现桩函数所需的参数信息;通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数模块。
2、 根据权利要求1所述的装置,其特征在于,所述缓冲区模块还用于存 储所述桩函数执行后的参数信息。
3、 根据权利要求2所述的装置,其特征在于,所述函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关系;所述参数信息包括出参值、全局变量值和返回值。
4、 一种利用权利要求1所述的装置在单元测试中实现通用桩函数的实现方法,其特征在于,包括被测函数存储步骤,用于存储被测函数;提取步骤,用于对存储的被测函数进行分析,获取所述被测函数的函数信息;符号表存储步骤,用于存储所述获取的所述被测函数的函数信息; 缓冲区存储步骤,用于存储实现桩函数所需的参数信息; 通用桩函数实现步骤,用于获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;驱动步骤,用于驱动所述被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数实现步骤。
5、 根据权利要求4所述的实现方法,其特征在于,所述缓冲区存储步骤 还用于存储所述桩函数执行后的参数信息。
6、 根据权利要求5所述的实现方法,其特征在于,所述函数信息包括数据结构、函数参数、返回值、全局变量和函数调用关系;所述参数信息包括出参值、全局变量值和返回值。
7、 根据权利要求4所述的实现方法,其特征在于,所述驱动步骤进一步 包括Fl,根据参数桩函数索引,获取桩函数名称; F2,获取所述桩函数的出参值、全局变量值和返回值; F3,设置被调用函数的出参值、全局变量值和返回值; F4,正确调整堆栈指针,返回控制权给调用函数。
8、 根据权利要求7所述的实现方法,其特征在于,所述F1之后进一步包 括Fll,调用UserStub函数,执行用户自定义代码。
9、 一种实现权利要求4、 5、 6、 7或8所述实现方法的测试系统,包括一 种在单元测试中实现通用桩函数的装置,其特征在于,所述装置包括被测模块,用于存储被测函数;提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测 函数的函数{言息;符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息; 缓冲区模块,用于存储实现桩函数所需的参数信息;通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信 息,设置对应的堆栈以及寄存器,并实现桩函数;驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数 在调用被调用函数时跳转到所述通用桩函数模块。
10、 根据权利要求9所述的系统,其特征在于,所述缓冲区模块还用于存 储所述桩函数执行后的参数信息。
全文摘要
一种在单元测试中实现通用桩函数的装置及其实现方法,该装置包括被测模块,用于存储被测函数;提取模块,用于对所述被测模块存储的被测函数进行分析,获取所述被测函数的函数信息;符号表模块,用于存储所述提取模块获取的所述被测函数的函数信息;缓冲区模块,用于存储实现桩函数所需的参数信息;通用桩函数模块,用于从所述缓冲区模块获取实现桩函数所需的参数信息,设置对应的堆栈以及寄存器,并实现桩函数;驱动函数模块,用于驱动所述被测模块中的被测函数,并使所述被测函数在调用被调用函数时跳转到所述通用桩函数模块。该装置减少了单元测试过程中桩函数的编写工作量,而且实现难度小,能够在各种操作系统、编译环境下实现。
文档编号G06F11/36GK101110055SQ20071012123
公开日2008年1月23日 申请日期2007年8月31日 优先权日2007年8月31日
发明者亮 马, 军 马 申请人:中兴通讯股份有限公司
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1