在对通信领域的
嵌入式系统作白盒测试时,
现有技术中通常使用以下 几种方法。
方法一,用C语言写桩与驱动,在调试环境下做测试。即用C语言编 写测试的桩函数及驱动函数,然后与被测对象一起编译,生成可执行文件, 然后嵌入式系统(如Vxworks、Psos等)的调试器中将可执行文件下载到 目标单板,启动运行后即自动调用驱动函数进行自动测试,测试结果通过 打印语句(printf语句)输出在调试器的输出界面上。
方法二,用C语言写桩与驱动,再构造一个起测试控制作用的测试终 端机。这种方法与方法一接近,也用C语言编写桩函数及驱动函数,然后 与被测对象一起编译生成可执行文件,不同的是可执行文件不通过调试器 下载,而是通过产品本身所支持的下载方式将被测试系统下载到目标单板 (或者直接烧片
固化于目标机内存),之后再构造一个独立的测试终端, 用作后台测试
控制器,测试终端通过TCP/IP通信连接,与目标机建立通 信机制,在此
基础上,测试终端通过自定义协议,进行测试起停、复位、 构造特定测试环境等操作。
方法三,用脚本写桩与驱动,在非实时系统中做仿真测试。该方法 的优点是容易构造测试的输入输出与测试过程控制,常见的方式是在 Windows操作平台上仿真目标机系统,支持多线程,但在该种情况下,实 时调度特性却没有了;如果要保留实时调度特性,则必须限制应用范围, 即只支持单线程。该系统能较好的实现测试自动化与测试度量。比如现有 技术中的工具(如Cantata与RTRT)就是使用此方案,即:首先使用一种 脚本语言描述测试控制(即用脚本写测试
用例);然后工具自动处理将脚 本转化为C代码嵌入到被测试源码中;编译执行后,在
用户界面就可以控 制测试执行。另外,该方法还提供
覆盖率插装与分析、支持测试程度度量 等多项功能。
方法四,用脚本写桩与驱动,在实时系统中做测试。这种方法与方法 三类似,不同之处是将插装后的系统下载到目标单板,再利用通信协议(如 TCP/IP)与测试终端建立通信关系,在测试终端的用户界面可以控制测试 过程。另外,该方法也支持覆盖率分析。
虽然上述四种方法在一定程度上满足了测试的要求,但仍然存在许多 缺点,具体如下:
对于方法一,虽然简单易用,但在调试环境下做测试,存在很大的随 意性,其过程可控性与可重复性较差,不利于自动化测试实现;
对于方法二,需建立测试终端与目标机间的控制协议,过程比较繁琐;
另外,对于方法一与方法二,还有因使用C语言写测试桩与测试驱动 而出现的效率低下、无法提供覆盖率度量、难以
支撑规模化、规范化、高 效率的测试等缺点;
对于方法三,由于在非实时系统中仿真实时系统,所以其实时特性必 然丢失,进而导致测试的不真实、测试范围很有限,仅在单元测试中用的 较好;
对于方法三与方法四,由于测试设计(编写测试脚本)与测试执行分 离的,所以具有动态交互特性较差、交互过于频繁的缺点,同时,该两种 方法在用例优化时无法避免关闭测试系统,
修改用例,再编译链接与重新 起动的过程,进而使用起来不方便,效率低下。另外,对于方法四,还需 频繁的下载目标机,操作更不方便。
可见,以上四种方法都难以解决多目标机相互配合的测试(如协议测 试),对于多目标机的测试,需要提供一种机制能快速的构造多个端点之 间的交互控制。
为解决现有技术的上述缺点,本发明的目的在于提供一种在嵌入式系 统中引入脚本化测试设计方法的,并在使用脚本的基础上可提升测试用例 即改即用特性的,可实现高效率自动测试的,并可为多目标机系统提供简 捷的多点测试控制方式的镜像测试方法。
本发明的技术方案在于,一种镜像测试方法,其特征在于,包括以下 步骤:(1)、在被测目标机系统内驻留一个脚本解释器,把所述被测目标 机系统的变量及函数映射到所述脚本解释器中,成为所述脚本解释器所能 认识与操作的镜像实体;(2)、用户使用脚本编写测试用例,再下载到所 述被测目标机系统的脚本解释器中去执行。
根据本发明所述的方法,在所述第(1)步中,按如下步骤实现实体 镜像:从被测目标机系统的被测试源码中提取被测实体的信息,包括各全 局变量的类型、函数的返回值和参数信息;在被测目标机系统内驻留一个 脚本解释器;再将所提取的信息在所述脚本解释器中结合符号地址重新生 成回镜像实体。
根据本发明所述的方法,在所述第(2)步中,如果所述被测目标机 系统在自己的域名空间找不到所述测试用例中的某个函数,则可通过服务 镜像技术,向测试后台
申请服务,由测试后台或其它处理机执行所述函数, 再由测试后台将函数返回值透传给申请服务的所述被测目标机。同样,对 于在自己的域名空间找不到的变量,所述被测目标机系统也可通过服务镜 像技术,向测试后台申请服务,对测试后台或其它处理机的所述变量进行 读或写。
根据本发明所述的方法,所述被测目标机可以是实时多任务系统,可 同时有多个线程在运行,所述脚本解释器所能认识与操作的镜像实体是线 程安全的,即允许多个线程同时操作镜像实体,能有效解决共享冲突问题。
根据本发明所述的方法,在用户编写的所述测试用例脚本中,可按相 同的脚本语法操作镜像实体,以实现脚本直接控制目标机变量与函数的目 的;所述被测目标机系统中驻留的所述脚本解释器可以是Python、Perl或 TCL,或者是其它脚本解释语言。例如在采用Python脚本系统对用C语言 编写的被测源码进行变量映射时,镜像由CVar(变量镜像),CData(数据 镜像)、CType(类型镜像)、CMem(内存镜像)四个类组成,其中CMem 用于记录从目标机中映射过来的内存序列;CType用于记录抽象实体的类 型,也即模板;CData用于对其归属的内存按特定模板进行解释,同时使 镜象实体参与Python运算,使变量镜象的前向与后向同步在这一层次得到 封装;CVar用于提供封装,使得结构、数组等操作类似于C语言的同类操 作。
与现有技术相比,本发明的镜像测试方法具有以下优点:
1、对比于方法二,本发明的镜像测试方法省去了测试终端与目标机 间繁琐的控制协议,提高了构造测试环境效率;
2、对比于方法一与方法二,本发明的镜像测试方法提供脚本化描述, 提高了测试用例设计与调试的效率;
3、对比于方法三,本发明的镜像测试方法在保证测试方便的同时, 不损失被测RTOS的实时特性;
4、对比于方法三与方法四,本发明的镜像测试方法在提供了一种规 范的、可度量的测试方法的同时,还支持更高效率的测试操作。另外,因 为脚本桩与驱动都是在线更新的,没有复位、重编译、重起动等过程,所 以交互特性更强,也更容易提供人性化的操作界面;
5、本发明的镜像测试方法可应用于多目标机相互配合的测试(如协 议测试、多处理机之间的集成测试等),其分布式远程调用体系,在测试 中使用非常方便,可大幅提高测试效率。
6、本发明的镜像测试方法实现了测试设计的脚本化,支持测试代码 自动加载并嵌入被测对象,这使得测试用例可独立于版本作维护,用户不 必在每出一个版本时,用手工将测试代码嵌入被测目标机系统。
附图说明
图1是本发明镜像测试方法的
流程图;
图2是本发明中实体镜像方法的原理图;
图3是本发明中变量镜像的四个组成类之间的关系示意图;
图4是本发明中操作保护过程的示意图;
图5是本发明中处理机之间的服务镜像的示意图。
本发明的镜像测试方法提供应用于白盒测试的一项技术,使宿主机能 通过脚本直接远程控制目标机,以实现高效率的自动测试,镜像测试方法 主要解决如下三大问题:
第一,在嵌入式系统中引入脚本化的测试设计方法。这里的脚本化测 试设计包括脚本化构造测试驱动与脚本化构造测试桩两方面,若测试设计 不是脚本语言,则其概括能
力差,使用烦琐,而使用脚本化测试表达可大 幅提高效率。
第二,在使用脚本的基础上,提升测试用例即改即用的特性。脚本语 言具备即改即用的特性,若用C语言(或其它编译语言)编写测试桩函数 与驱动函数,就必需编译、链接,再运行起来才起作用,这对频繁的测试 操作非常不方便。在此基础上,镜像测试方法提供一种机制,保证了使用 脚本写测试控制(脚本桩与脚本驱动)完全等效于用C语言编写,而且脚 本桩与脚本驱动在嵌入被测目标机系统后可在线更新链接关系。
第三,为多目标机系统(如协议测试场合、多单板间集成测试)提供 简捷的多点测试控制方式。镜像测试方法通过在各个目标机驻留脚本解释 器,然后使用分布式的多解释器系统构造出远程调用体系,这样就简化了 多点控制的测试逻辑表达。
本发明的镜像测试方法的流程如图1所示,其中:
步骤101、从被测目标机系统的被测试源码中提取被测实体的信息, 包括各全局变量的类型、函数的返回值和参数信息;
步骤102、在目标机中驻留一个脚本解释器;
步骤103、将提取的信息在脚本解释器中结合符号地址重新生成回镜 像实体,也就是把被测目标机系统的变量及函数映射到脚本解释器中,成 为脚本解释器能认识与操作的镜像实体;
步骤104、用户使用脚本编写测试用例;
步骤105、将测试用例下载到被测目标机系统的脚本解释器中;
步骤106、脚本解释器执行测试用例,实现测试功能;
步骤107、108、如果被测目标机在自己的域名空间找不到的函数或变 量,则可通过服务镜像技术调用其它处理机的函数或变量,再由测试后台 将返回值透传给被测目标机,然后回到步骤106继续执行测试操作。
可见,本发明的方法主要包括实体镜像与服务镜像两大内容,下面将 分别描述方案要点。
一、实体镜像实现方案
其中实现镜像测试方法需在目标机中驻留该脚本解释器,通过实体镜 像把C语言的变量与函数映射为脚本的变量与函数,实现映射时需提取被 测实体中各全局变量的类型、函数的返回值、参数等信息。通过对被测源 码的词法、句法、语义分析,分离出实体的内存与类型,在测试系统起动 后,再结合实体的内存与类型创建脚本实体以达到映射的目的。对被测源 码进行词法、句法、语义分析后,生成抽像语法树,该语法树用于提取必 要的信息(用于实体镜像与服务镜像),语法树反向生成回源码,用于编 译成执行文件,该执行文件还包括了服务于镜像技术的目标机符号表(也 即变量与函数的地址表)。
如图2所示,在被测目标机系统内驻留一个脚本解释器(图中左半部 分),该解释器与测试后台以TCP/IP方式建立通信连接。实体镜像是指被 测目标机系统中的变量与函数从C目标码映射到脚本解释器中,成为脚本 解释器所能认识与操作的镜像实体。实现实体镜像之后,用户使用脚本编 写测试用例,下载到目标机解释器去执行,在用户编写的用例脚本中,可 以按相同的脚本语法操作镜像实体,以实现脚本直接控制目标机变量与函 数的目的。目标机中驻留的解释器可以是Python、Perl或TCL,下面假定使 用Python脚本系统以说明整个实现过程。
1、变量镜像方案
1-1变量映射
变量镜象由CVar、CData、CType、CMem四个类组成。如图3所示,CMem 记录从目标机中映射过来的内存序列,CType记录抽象实体的类型,也即 模板。CData提供对其归属的内存按特定模板进行解释,也同时使镜象体 参与Python运算,变量镜象的前向与后向同步在这一层次得到封装。CVar 是适配套,CData提供的操作与C语法操作有一定差别,CVar提供封装,使 得结构、数组等操作用起来象C语言一样。
CMem可完整地映射目标机的内存值,当一个CMem实体新创建时, 即先初始化其占用的内存空间,各内存字节赋予初始值(先完成一次读操 作)。CMem提供以下方法:
*自动刷新(同步操作);
*在某偏址之后按
指定数据类型读取变量值(读操作);
*在某偏址之后按指定数据类型写某变量值(写操作);
*在某偏址之后按字节写内存值(Assign操作)。
1-2变量的模板
变量的模板用于描述变量的数据类型,应覆盖所有C语言支持类型, 变量模
块也用于描述函数的模板,函数的参数与返回值由变量模板指示。
按照变量的使用特性,本发明作如下分类:
(1)、非
指针*基本数据类型;
*结构(联合);
*数组;
(2)、指针
*基本类型;
*结构(联合);
*数组。
基本数据类型指int、long、float等直接应用于C语言运算的数据类型; 结构(联合)与数组是复合数据类型,模板描述各类型的子集特性与格式。 对于基本数据类型,其基本组成单位是Byte,子集特性描述其占用Byte的 数量,格式则描述其应用于Pack、Unpack的格式;对于结构或联合,子集 特性是该结构的成员列表,格式是该结构成员的类型列表;对于数组,子 集特性是其元组个数,格式是元组类型。根据子集特性与格式,可得到该 数据类型的占用的空间大小。CType提供以下方法:
*取子集类型(子集指结构或联合的成员,数组的元组);
*取子集偏移长度;
*类型比较;
*在某偏址之后按字节写内存值(Assign操作)。
1-3变量映射与读写机制
CMem类实例保存目标机变量映射的内存序列,CMem提供read(读) 与write(写)函数以解释该内存提供的操作,这两个函数的定义如下:
def read(self,offset,type)
def write(self,offset,type,value)
其中,read函数将CMem实例镜象内存的offset偏址解释为类型为type 的变量,读取其值;write函数则相反,把类型为type的值value写入镜象 内存的offset偏址。
1-4镜像变量参与脚本运算
Python系统的变量类型比C语言提供的要少,但能覆盖全部C语言的 变量描述。Python的基本变量类型有int,long,string,float: Data Type Number of bytes Sign Cover C Tpe Int 4 unsigned/signed Char,Byte,unsigned byte,int Long No limit unsigned/signed Unsigned int,long,unsigned
long String No limit N/A String Float 8 N/A Float,double
参与运算时,多字节的类型可表达少字节类型的数据类型,如,int类 型可完整的表达char与byte的类型,double类型可完整表达float类型的 变量。如此,本发明使用上述4类Python数据类型描述从CMem实例中 读出的数据值,然后这4种数据类型参与Python体系运算,往写CMem 实例写变量也如此,也是这4种数据类型。
由于Python语言为类对象参与数学运算提供了钩子函数,如:1+a,a 是类对象,实际是调用a的_add_函数完成运算,a+1调用a的_radd_ 函数,a+=1则调用a的iadd_函数完成,其它数据类型的运算类似。本 发明可以定义镜象变量的class类,并赋予运算函数,镜象变量就可参与 Python体系的运算了。
2、函数镜像方案
C语言的函数经编译之后,调用关系表现为以下形式:
函数A:
....
函数参数压栈(PUSH arg1);
函数参数压栈(PUSH arg2);
CALL函数B;
....
函数B:
保存相关寄存器;
从堆栈中取参数;
函数过程处理;
设置返回值到寄存器;
恢复相关寄存器;
ret;
假设调用函数B要带两个参数,函数A运行中要调用函数B,先将这 两个参数压栈,然后使用CALL指令调函数B,函数B运行结束使用ret 指令返回函数A。
脚本解释器要调用目标机函数时,依次执行以下步骤:
*将值列表压栈;
*CALL相关函数;
*从寄存器取返回值。
从寄存器取出的返回值,需生成回脚本变量返回给系统。
二、脚本桩实现
本发明镜像测试方法使用打
补丁技术,使被测代码更改流向,若被测 目标机系统中某函数被打补丁,它被调用时就直接跳转到脚本系统,由脚 本系统提供处理函数以替换原函数执行。这项技术叫脚本桩技术,使用这 种技术,可以直接用脚本写测试用的桩函数。
脚本桩技术支持两种运行方式,即覆盖运行方式(脚本桩函数替换C 函数执行)与插入运行方式(脚本桩插在C函数之前运行,被补丁的C函 数仍要运行)。
三、多线程支持
本发明从两方面考虑对多线程支持的需求,一是对解释器内部数据类 型的操作保证多线程下是安全的,二是封装RTOS(实时
操作系统)通用 的线程服务,包括封装互斥量,
信号量、事件等实体,还要封装出取线程 ID、启动线程等函数。
因为测试脚本经过伪编译加载到前台,前台逐个读解伪编译码的 Token,得到数据与操作指令。本发明所支持的内部脚本数据类型的操作 是线程安全的,需保证一次运算时组织数据(输入过程)与对它的操作是 受保护的,这种保护是多个脚本线程(脚本线程特指使用解释器时的线程, 它本质是操作系统的线程,不是特别的东西)对同一实体(只能是脚本数 据类型)同时操作带来的冲突进行保护。脚本线程与C线程对C变量操作 的冲突是应用层次的线程同步,用户可使用经封装的信号量实现保护,这 是另一个层次的概念。
如图4所示,一次操作保护过程是在需要保护区段先申请Lock量, 完成操作时再释放该Lock。这是一次
原子操作的保护过程,再优化一下, 因为本发明只考虑脚本实体被多个脚本线程使用时的共享冲突,为限定多 脚本线程独占或频繁切换,解释器里设有一个互斥量,当前只允许一个线 程在跑(得到运行前申请互斥量),只有该线程主动释放该互斥量,其它 线程才可能运行。这样,只要当前脚本线程不在原子操作未结束时释放该 互斥量,就一样实现了操作保护。这种方式可以不用频繁申请/释放Lock 量,一次保护就是一大段操作,至于当前线程何时递交控制权,可以设置 计数器,比如每连续20次运算就释放一次控制,当然若此时没有其它线 程要运行,该线程也没完成全部运算,它会再次得到控制继续执行下去。
为支持上述多线程切换,本发明还需要对解释器的数据栈进行保护, 多个脚本线程被切换的
位置是不确定的,它的调用栈深也不确定,需要保 护,这样才保证线程切换回来时能正确的往下执行。本发明在解释器
内核 设置脚本动态栈,视多少线程使用脚本调用,有多少个线程调用,就有多 少个动态栈。当脚本线程得到控制权时,先取自身的线程ID,再根据这个 线程ID找到属于自己的动态栈。
支持线程的另一个需求是封装RTOS的相关线程实体,这项工作相对 简单,所要达到的目的是用脚本控制线程与C代码控制线程,效果等同。 下面以信号量对象为例说明如何封装。
与信号量相关的操作包括:创建、删除、P/V操作,假设操作系统已 提供如下函数:
UINT32 SmCreate(CHAR*pchSmName,UINT32 ulSmInitialNum,UINT32 ulFlags,
UINT32*pulRetSmID);
UINT32 SmDelete(UINT32 ulSmID);
UINT32 SrnP(UINT32 ulSmID,UINT32 ulTimeOutInMillSec);
UINT32 SmV(UINT32 ulSmID)
本发明使用Object表示信号量(在C语言中是一个unsigned int数值, 只在特定函数调用时传入该ID值才显得这是一个信号量),该信号量的ID 与当前值封装在该Object里,再提供几个类方法:
SM.P()//信号量P操作;
SM.V()//信号量V操作;
SM.getID()//取信号量ID值。
此外再封装建构与析建函数,这就构成了简单的脚本对象。使用如下: 使用脚本 实际调用C函数 说明 Sm1=SM(8,″MySm″); SmCreate 创建一个信号量,名为“MySm”, 初始值8,成功时信号量ID值保 存在Object结构里。 Sm1.p() SmP 信号量Sm1的P操作 Sm1.v(); SmV 信号量Sm1的V操作 Sm1.getID(); 返回保存在Object里的ID 值 取信号量ID值 del Sm1; SmDelete 删除信号量对象
可见,本发明对信号量的封装,只是适配性封装,本质上还是调用相 同的C函数,使用效果与C语言完全等效。这样脚本系统与目标机一起运 行时,两者可互相融合。
四、服务镜像方案
如图5所示,一个父处理机与多个子处理机构成了分布式处理系统, 服务镜像有三层含义:
*父处理机可得到任意子处理机的过程服务(读写变量与调用函数);
*任意子处理机可得到父处理机的过程服务;
*任意子处理机可得到其它子处理机的过程服务。
在图5中,父处理机相当于测试后台,子处理机相当前台的多个目标 单板,每个前台在测试后台映射为一个端口,比如在测试后台调用处理机 1中的函数getCrcSum,后台执行portl.getCrcSum()即可,这个函数实际 是在处理机1中执行的,但函数参数由后台传入,函数返回值也返馈回后 台。
当子处理机在自己的域名空间找不到某个函数,它会向测试后台申请 服务,然后挂起自身线程等待函数返回值,后台完成服务将返回值返回给 前台相应子处理机,该处理机就继续往下执行。子处理机得到其它处理机 的服务也类似,通过父处理机透传完成的。比如,子处理机2想调用子处 理机1中的函数getCrcSum,执行portl.getCrcSum(),子处理机2在本地 找不到portl.getCrcSum,就向父处理机申请函数调用服务,父处理机执行 portl.getCrcSum(),从处理机1中获得服务,取得函数返回值,再透传给 申请服务的子处理机2。
值得注意的是,服务镜像不局限于函数调用,也适用于变量读写,如, 上例中处理机2想得到处理机1的变量CrcNum,执行portl.CrcNum就可 以获得该变量值,原理是一样的。变量赋值也类似,处理机2执行: portl.CrcNum=0,实际是将处理机1中的变量CrcNum复位。在支持服务 镜像的体系中,各处理机之间需要透传实体(函数参数与返回值),有两 个前提,一是该实体能够自描述,二是各处理机驻留相同的模板(基本数 据类型或类定义),驻留相同的模板是保证透传实体的自描述能正确的被 解析。
若在远程运算过程中出现了异常,远端透传回一个Exception对象(以 文本方式自描述),调用发起端收到该响应就发出一个Error(出错信息)。 这一机制保证了异常处理也能准确透传。
五、提供覆盖率度量
由于实现实体镜像需从源码中提取C变量与C函数的类型信息,本发明 需对被测试源码做预处理、词法分析与句法分析,生成抽象语法树。抽象 语法树不仅用于提取镜像信息,也用于插装分析,生成插装代码,以支持 白盒测试中的覆盖率分析。
利用本发明的镜像测试方法开发测试工具,可大大简化测试过程,提高 测试效率,有力地促进测试自动化发展;其中将测试代码从被测代码中分 离出来,推动了可测试性技术更自由的发展;所支撑的远程调测与问题定 位可大幅节约产品的维护开销。