陈 建,沈亚峰,张 谊
(中国工程物理研究院 计算机应用研究所,四川 绵阳 621900)
随着电子技术的发展,嵌入式软件规模越来越大,复杂度也越来越高,软件质量要求也不断提高,嵌入式软件的自动化测试[1,2]以其提高测试效率、保障测试质量等优点越来越受到关注。但随着嵌入式软件处理能力的增加,其接口类型更多、结构更复杂,主要体现在业务逻辑与接口数据方面,且不同类型的嵌入式软件接口存在较大差异性,造成测试数据难以统一,为确保测试充分性需构造大量、复杂的测试数据进行测试,为此针对嵌入式软件测试能够实现测试数据快速生成与输入的自动化测试框架显得更为重要。
嵌入式软件的输入数据类型众多,包括数字信号、模拟信号以及CAN、UART、LAN等总线数据,测试时需对多种激励信号进行输入,另外在强流程嵌入式软件中,其输入信号之间具有严格的时序要求,通常为毫秒级,测试数据的输入时序控制也成为嵌入式软件测试过程中的重点与难点。目前成熟的嵌入式软件的自动化测试框架如CRESTS、AUTOTEST、GESTE[3]等在使用过程中均需编写复杂的测试脚本以实现测试数据的输入,使用门槛较高,测试环境搭建周期较长,不利于快速测试,为此本文主要对嵌入式软件测试框架进行研究,设计一款轻量级嵌入式软件自动化测试框架以实现测试数据的快速生成与输入,提高测试效率。
目前,针对嵌入式软件的测试框架可分为手工测试框架和脚本驱动测试框架。
手工测试框架:手工测试框架主要采用手动输入的方式将测试数据填写到测试工具中,再利用工具输入到被测软件(如:NetAssist、UartAssist等),测试数据的输入具有单一性与无序性,需要输入不同数据时则需要重新填写数据,在数据量比较大时,需要花费大量时间,另外输入的时序无法保证,无法用于具有时序要求的嵌入式软件测试。
脚本驱动测试框架:脚本驱动测试框架可分为线性脚本、结构化脚本、共享脚本、数据驱动脚本、关键字驱动脚本以及混合驱动脚本[4,5]。
(1)线性脚本:线性脚本是通过测试工具录制测试人员操作被测对象的测试动作、输入数据,并记录在脚本文件中。该脚本一般采用特定的语言记录测试动作序列,脚本可进行回放,线性脚本的优点是简单,通过录制即可实现,无须编写代码,缺点是仅适用于当前被测软件,不能共享与重用,可维护性差,仅适用于部分功能。
(2)结构化脚本:结构化脚本是对线性脚本的优化,支持通过编程指令控制脚本执行,脚本具有逻辑判断能力,根据指令判断脚本执行顺序,相对线性脚本在一定程度上灵活性、健壮性得到改善,并可用于重复性的自动化测试,如Python[6]、ATLAS[7]、TTCN[8]、RASL[9]等脚本语言,但结构化脚本将逻辑指令与测试数据融合,当测试数据需要更新时需重新编写测试脚本,测试脚本的开发工作量大幅度增加,另外测试场景越复杂,脚本编写难点越高,维护代价越大,不利用测试工具的普及与应用。
(3)共享脚本:共享脚本在结构化脚本的基础上对脚本进行完善,使脚本可复用,节省了软件测试的时间,但测试数据仍未从脚本中进行分离,当多个测试用例需要复用同一测试脚本时,脚本开发、维护工作增加。
(4)数据驱动脚本:数据驱动测试将测试输入存储在独立的数据文件而不是与测试脚本紧耦合。脚本中只存放控制信息,执行测试时从外部数据文件中读取测试数据,一个测试脚本可以运行不同的测试,避免脚本驱动测试频繁修改测试脚本的不足。数据文件单独存储且可读性、可维护性高,数据与脚本的分离减少测试脚本的维护成本,提升测试效率[5],但数据驱动脚本测试框架最大限制是数据格式单一、固定,无法适应数据动态变化的情况。
(5)关键字驱动脚本:关键字驱动脚本在采用数据文件描述测试用例的同时,通过一系列关键字描述测试任务,同样实现了测试脚本与测试数据的分离,但仅通过关键字驱动会随着软件复杂度增加而增加大量关键字,构建成本较大。
(6)混合驱动脚本:采用多种脚本技术协同使用,可以有效避免单一脚本带来的局限性,弥补单一脚本的不足。
根据以上分析,数据驱动与关键字驱动脚本测试框架均实现了数据与脚本的分离,仅通过构建测试数据即可实现不同测试数据的输入,无需重复编写测试脚本,提升了脚本的复用性、维护性,国内外基于该技术也做了很多研究[10-15]。
文献[11]采用基于模型思想,提出一种字符型驱动形式化描述通用方法,建立基于时间的PWM驱动模型实现嵌入式软件接口驱动的分析与验证,但该方法仅能用于单一的接口验证,无法用于软件的业务流程的测试。文献[12]将BDD的思想应用于数据驱动的测试中,使用通用的自然语言描述测试用例,该思想注重从用户的角度进行测试,针对复杂的测试场景,其测试场景与步骤的描述显得比较繁琐,需花费大量时间。文献[13]采用数据驱动方式实现网口的自动化测试,通过数据文件实现测试数据的注入,但当被测对象输入数据动态变化时无法自适应输入且不支持其它接口。文献[14]与文献[15]采用数据驱动思想实现了自动化测试框架,可通过文件快速构建测试数据,实现测试数据的自动化注入,提高测试效率,但该框架仅对Web软件数据进行分析,构建数据模型实现Web软件自动化测试,无法用于嵌入式软件的强时序复杂接口数据的注入。
综上分析,本文介绍了一种将数据驱动与关键字驱动相结合的混合脚本测试框架,在该框架下即可实现嵌入式软件测试数据按照前置条件、时序快速主动与被动输入,也可利用关键字实现测试数据的动态自动生成。框架设计主要存在以下难点:①测试数据复杂多样性;②测试数据动态自适应。
搭建嵌入式软件自动化测试框架的重点是要快速构建该嵌入式软件的数据流程以及行为参数[16]。随着嵌入式处理器复杂与多样化,嵌入式软件需要的测试数据也越来越复杂,为满足不同嵌入式软件的测试,采用数据驱动的思想构建统一的数据模型表达测试数据,以实现数据的快速输入。
另外,根据被测软件业务要求存在动态变化的测试数据,如握手数据、连续数据、离散数据等,为确保对测试数据的自适应,采用关键字驱动的思想构建关键字模型以表征动态数据,用于实现测试数据自动生成。
最后对测试框架进行设计与实现,并通过实例对框架的功能、性能进行验证与分析。
基于数据与关键字驱动的测试框架是通过数据文件读取输入的测试数据与关键字,然后传入测试脚本中,由测试平台调用测试脚本解析测试数据并按照时序向被测对象输入测试数据。为确保测试输入的高效与统一需建立通用的数据模型对测试数据结构进行描述。测试数据模型是抽象出来独立于测试平台的测试数据,存储于结构化的文件中,通过标签属性,获取这些文件中符合条件的指令和配置数据。本文介绍的数据与关键字驱动测试框架的数据模型包含数据驱动模型与关键字驱动模型。
通过对嵌入式软件输入数据的规则与要素进行分析,构建通用输入数据模型。
定义1 输入数据模型 (SD):SD=
Port:表示数据输入的端口类型,包括:LAN、CAN、UART、1553B等;
Ts:表示数据输入的延时时间,用于控制数据输入时序;
R:表示数据输入的次数,用于表示单次数据、周期数据;
Tc:表示数据输入间隔时间;
Prot:表示数据协议要求,根据数据源的不同分别按照对应的数据协议要求进行设置,如LAN则表示数据输入的IP地址与端口号,CAN则表示数据类型、帧ID等;
Data:表示待输入的数据信息,包含固定数据(FD)与关键字数据(KD)。
根据输入数据是否存在前置条件可分为主动输入数据与被动输入数据。
主动输入数据:主动输入数据即测试平台通过测试脚本解析数据文件后,可在任意时刻按照设定的时序要求向被测对象直接输入的数据,不考虑其前置条件。主动输入数据(ADChain)是由一系列SD按照时序关系组成的有序集合 {SD1,SD2,…,SDn},针对任意i(1≤i≤n),SDi与SDi+1之间均具有相继发送的时序关系,通过若干个SD的有序发送即可表达被测对象的主动输入行为。
被动输入数据:被动输入数据即在满足特定的前置条件后才向被测对象输入的测试数据,针对嵌入式软件其前置条件一般为输出条件。
定义2 前置条件(PC):PC=
定义3 响应数据(RD):RD={SD1,SD2,…,SDn},一个响应数据是由多个输入数据构成的集合,根据前置条件触发的次数依次进行输入。
单条被动数据由一个前置条件与若干个响应数据组成,若干条被动数据的集合则构成整个被动输入数据(PDChain),即PDChain={{PC1,RD11,RD12,…,RD1m},…,{PCn,RDn1,RDn2,…,RDnm}},其中n为被动数据条数,m为响应个数。当获取到被测对象输出数据后,对PC1~PCn进行遍历,若判断结果PCResi=1(其中1≤i≤n)则将响应数据RDi1~RDim按照设定的时序注入到指定的目标端口即完成被动数据的输入。
在数据输入过程中存在变化的动态字段,针对动态字段若仅依靠数据驱动则需要构建大量的数据,花费大量时间,另外在被动输入数据中存在随目标数据变化的字段(如握手信息、校验码等),数据驱动无法预知其具体值,无法注入满足要求的数据,为此在数据驱动模型中加入关键字,通过数据与关键字驱动相结合的方式实现测试数据的自动生成与输入。
根据嵌入式软件输入数据的特点从数据位置、长度、类型、排列方式等角度进行分析,构建关键字模型。
定义4 关键字模型(KD):KD=
Key:表示数据生成方式,包括握手数据、时间数据、连续数据、离散数据、校验数据、特殊功能数据(如长度)等;
Type:表示数据类型,包括有符号整形、无符号整形、浮点型等;
BW:表示数据位宽,BW∈{8,16,24,32,…};
Mode:表示数据排列方式,包括小端排列与大端排列;
Pos:表示数据的范围或数据的起始与终点位置;
Exp:表示数据计算表达式f。
测试数据生成规则如下:根据Key获取生成数据的类别,选择相应的生成方式;根据Pos表示的范围或区间位置获取初值,根据选择的生成方式与f计算当前数据值;根据Type、BW以及Mode将计算值填充至指定的字段域。
以数据模型为基础对该测试框架整体架构进行设计,采用模块化的思想对各个模块进行设计,保证模块的独立性与可扩展性。框架整体结构主要包括数据解析模块、被动输入模块、主动输入模块、接收数据模块、发送数据模块以及显存模块,具体组成如图1所示。
图1 自动化测试框架整体架构
基于上述设计方案,采用VC6.0研制了该测试框架,采用多线程与环形缓存原理保证各个模块之间既能协同工作,又能实时数据传递,框架运行界面如图2所示。
图2 自动化测试框架运行界面
其中数据解析模块与数据输入模块是该框架的关键模块,是数据与关键字驱动的测试数据输入思想的具体实现。
3.2.1 数据解析模块
该框架以文本文件作为测试数据的输入,在文本文件中存储待输入的主动数据与被动数据,软件按照设定的规则对文件进行解析,形成主动输入数据链(ADChain)与被动输入数据链(PDChain),其中ADChain解析实现过程如下。
Algorithm:LoadADChain
Input:(strPath) /*Active Test file Path*/
Output:(count) /*The count ofADChain*/
(1)p_file = OpenFile(strPath,"r");/*open file*/
(2)whileget a line from p_file and line is not nulldo/*read a line*/
(3) SplitStringToArray(strArray,line," ");/*split the line by space*/
(4)ifstrArray.GetSize() <4then/*check the line*/
(5)exitwhile;
(6)end
(7) pSD = ADChain.AddSDList();/*createSD*/
(8) …/*read thePort、St、Rn、Pre…*/
(9) AnalysisProtocol(strArray[4],pSD); /*analysis and update the protocol*/
(10) SplitStringToArray(strArray,line.right(strArray[5])," ");/*split the data*/
(11)fori ← 0 to strArray.GetSize()-1do/*analysis the keyword data and fix data*/
(12)ifis theKDthen/*isKD*/
(13) pKD = pSD.AddKDList();/*createKD*/
(14) pKD.AnalysisKD(strArray[i]);/*analysis theKD*/
(15) …/*update theKD*/
(16) pSD.Data = 0;/*fill the 0*/
(17)else/*is FData*/
(18) pSD.Data = strArray[i];/*fill the fix data*/
(19)end
(20)end
(21)end
(22)returnADChain.count;
PDChain解析实现过程如下。
Algorithm:LoadPDChain
Input:(strPath) /*Passive Test file Path */
Output:(count) /*The count ofPDChain*/
(1)p_file = OpenFile(strPath,"r");/*open file*/
(2)PDNum = GetPDCount();/*get thePDCount from file*/
(3)fori ← 0 to PDNum-1do
(4) pPD = PDChain.AddPDList();/*createPD*/
(5) pPD.PC = GetPC(p_file);/*read thePC*/
(6)forj ← 0 to RDNum-1do/*read theRD*/
(7) pRD = pPD.AddRDList();/*createRD*/
(8)fork ← 0 to SDNum-1do
(9) pSD = pRD.AddSDList();/*createSD*/
(10) …/*read thePort、St、Rn、Pre…*/
(11) AnalysisProtocol(p_file,pSD);/*analysis and update the protocol*/
(12) …/*analysis the keyword data and fix data*/
(13)end
(14)end
(15)end
(16)returnPDChain.count;
数据解析与显示界面实现结果如图3所示。
图3 数据解析与显示界面
3.2.2 数据输入模块
数据输入模块按时间序列对各输入数据独立进行管理,当数据输入时刻到达后根据关键字自动生成测试数据并注入到指定的端口,包括数据输入时刻计算与数据自动生成。
数据输入时刻计算:针对任意输入数据,其数据输入时刻Ti的计算如下
Ti=T0i+Tsi+j×Tci
(1)
式中:i∈[1,n],j∈[0,Ri],n表示输入数据的个数,Ri表示第i个数据输入次序,T0i表示相对零点时刻的间隔时间,Tsi表示数据输入的延时时间,Tci表示数据输入的间隔时间。针对T0i的计算,主动数据与被动数据计算方式不同。
主动数据:主动数据无前置条件,严格按照时序执行,无须等待,故T0i=0;
被动数据:被动数据存在前置条件,需等待前置条件满足,故当第i个被动数据的前置条件满足后以当前时刻t为T0i,即T0i=t,在T0i未取值前不进行Ti的计算。
当Ti=t时将计算后的数据注入到指定端口进行发送,随后j=j+1,继续计算Ti直到j>Ri则表示该数据输入完成。
数据自动生成:数据的生成包括固定数据(FD)生成与关键字数据(KD)生成,生成的数据为多个十六进制数据组成的集合 {d1,d2,…,dn}。固定数据直接取值即可,即di=FDi,其中di表示第i个字段。
根据关键字数据生成规则,其生成过程如下:
初值计算:根据关键字中的Key与Pos计算初值y0,具体如下:
握手数据:直接从接收数据中取值,即y0=Rs~Re,其中R表示接收数据集合,s与e是Pos中表示的数据起始与终点位置。
时间数据:直接根据时间规则计算时间,即y0=t,其中t可表示当前日期、时间等。
连续数据:从数据范围的s→e逐次获取数据,并逐次加1,即
(2)
离散数据:直接从Pos表示的序列中获取数据,即y0∈{Pos1,Pos2,…,Posn},根据发送次数逐次获取。
校验数据:计算指定区间s→e字段的校验值,即y0=Fc{ds,ds+1,…,de},其中Fc表示校验运算,包括和校验、CRC校验、异或校验等。
特殊功能数据:根据功能字段直接计算数据,如长度关键字(len)则表示取发送数据长度,即y0=n,其中n表示发送数据长度。
表达式计算:将计算的初值y0与表达式f进行加权计算y=f1×y0+f2,其中f1为缩放表达式,用于对初值进行缩放,f2为偏移表达式,用于对初值进行偏移修正,支持线性与非线性表达式。
再根据Mode填充数据,填充序列如下
(3)
(4)
数据输入模块的实现过程如下。
Algorithm:SendData
Input:(rxd,t)/*The receive data rxd,The time t*/
Output:(txd) /*The send data txd*/
(1)txd= null;/*set the txd is null*/
(2)/*check the the Passive data timing*/
(3)fori ← 0 to PDChain.Countdo
(4) PCRes[i]=CheckPC(PDChain[i],rxd);/*checkPC*/
(5)ifPCRes[i]== 1then
(6) T0[i]=t;/*update theT0*/
(7) …/*add toSChain*/
(8)end
(9)end
(10)/*check Ti and create txd*/
(11)fork ← 0 to SChain.Countdo/*include theADChainandPDChainwhich the T0≠0*/
(12)ifT[k]== tthen/*timing is coming*/
(13)fori ← 0 to SD.Countdo/*calculate data*/
(14)ifSDisFDatathen
(15)txd[i]= FD[i];
(16)else/*isKData*/
(17) y0=Calcy0(KD[i]);/*calculate y0*/
(18) y=Calcy(KD[i],y0);/*calculate y*/
(19)txd=FillData(KD[i],y);/*Fill Data*/
(20)end
(21)end
(22) SChain[k].inx++;/*send sequence*/
(23)ifSChain[k].inx <= SChain[k].Rthen
(24) T[k]=T0[k]+SChain[k].Ts+ SChain[k].inx × SChain[k].Tc;/*calculate the nextTi*/
(25)else
(26) …/*remove the SChain[k]fromSChain*/
(27)end
(28)end
(29)end
(30)returntxd;
通过某监控嵌入式软件的状态查询与静检功能对该框架进行验证,监控软件运行后周期接收下位机发送的自检结果“55 AA 01 …”与状态信息“AA/BB 7E …”并进行解析,另外可主动发送静检命令“55 AA 03 …”、“55 81 02 00”,待下位机收到静检命令后被动反馈应答“55 AA/BB …”及静检结果“AA 7E …”。首先根据监控软件状态查询与静检功能的输入输出数据流构建如图4需要发送的输入数据文件。
图4 输入数据文件
其中字段“len[]”表示特殊功能数据的长度数据,取数据字段的总长度;字段“time32[]”表示时间数据,取系统当前时间;字段“cnt16[0~10]*10”表示连续数据,数据范围为[0~100],间隔10;字段“sum8[0~len-2]”表示校验数据,计算指定区间数据的和校验;字段“rec[3~4]”表示握手数据,表示取接收数据的第3~4字节。
运行该测试框架,读取数据文件,发送数据,框架实例化各关键字生成相应的测试数据并按照时序逐次向监控软件发送数据,具体结果如图5所示。
图5 主动发送数据结果
另外对监控软件发送的目标数据进行接收,当接收到静检命令后进行前置条件判断,满足要求后生成测试数据并按照时序要求向监控软件进行被动发送,具体结果如图6所示。
图6 被动发送数据结果
对发送的数据的时间性能进行分析,具体见表1。
表1 数据发送时间测试结果/ms
另外,再通过其它工具实现图4中的数据发送行为,其操作结果对比见表2。
表2 不同类型工具操作结果对比分析
根据测试结果可知,该自动化测试框架能够正确解析数据文件,实现测试数据的主动与被动输入,自动实例化关键字并生成测试数据进行发送,发送数据内容、时序正确,数据处理效率在毫秒级,能够满足嵌入式软件数据处理实时性要求。
本文将数据与关键字驱动思想应用到嵌入式软件测试中,设计并研制了一款轻量级嵌入式软件自动化测试框架。相较于传统嵌入式软件测试框架,该框架能够控制测试数据的输入时序与条件,同时可实现测试数据的自适应生成;相较于专业嵌入式软件测试框架,该框架将测试数据与测试脚本完全分离,省略了测试过程中复杂的测试脚本编写过程,降低了测试环境搭建时间与难度,另外简明、格式化的数据结构能够使测试人员快速构造测试数据进行测试,提升了测试效率,工程实用性较强。
根据实例验证可知,该框架可用于嵌入式软件测试,并已在多个嵌入式软件测试中进行应用。但该框架目前测试数据自动生成策略尚未结合测试设计方法,后续将测试设计方法融合其中实现基于模型的测试数据自动生成,以适应复杂系统的测试。