天天看点

专家笔记:关于ETestDEV测试脚本开发模式的考虑

作者:凯云科技

1需求的提出

ETestDEV中对于从通道中接收报文数据的处理中,有一种处理方式为:

on_buff_recv(channel,fun),其中的fun为接收到buff的回调函数,其定义的原型应为:

function fun(channel,buff)

end

在这个回调函数中,buff为传进来的接收到的缓冲区。

在操作系统中,通道channel传送过来数据,会产生一个消息,这个消息用于给应用进行处理响应。

假设被测件为U,它对外定时向外发送两种类型的协议包,分别为ProtoA和ProtoB,ProtoA和ProtoB以不同的时钟周期发送,每10ms向外发送一帧ProtoA,每20ms向外发送一帧ProtoB;除此之外,被测件U的部分功能还会根据不同的用户操作随机性地向外发送协议包ProtoC。

对于测试设备T而言,T就需要使用On_on_buff_recv(channel,fun)处理从被测件U发送过来的数据,这些数据中夹杂着协议包ProtoA、ProtoB、ProtoC,在这些个协议包中还有可能中间存在着脏数据。

那么问题来了,如何在fun(channel,buff)处理数据的协议包解析,才能确保解析速率与编程模式都最佳呢?

2ETestDEV对解包的支持

2.1 默认提供的解包函数

在ETestDEV中,提供了使用协议定义从数据缓存解析出协议报文数据的API,其原型如下:

result = unpack(prot,buff,is_auto_shift)

输入参数:

prot:EProtocol 类型,协议对象

buff:EBuff 类型,数据缓存

is_auto_shift:boolean 类型,解包成功后是否自动移除使用过的字节

返回值: result:dict 类型,解析结果字典 result.value:报文数据值 result.size:解包使用的字节长度 result.prot:解析时使用的协议名称 result.left:解包完成时还剩余未使用的字节数 result.skip:开始解析之前忽略的字节数 result.valid_fail_segs:自动验证失败的协议字段名称。

这个解包函数只能从buff中解析出符合某个协议的报文。

2.2 解包的需求及其简单实现

如果按照被测件U对外发送数据的需求,在某一个on_buff_recv(channel,fun)的响应函数中,其buff可能是 XX XX XX XX ProtoA ProtoB XX XX ProtoC ,也可能是ProtoC XX XX ProtoA XX XX ProtoB。其中XX可以代表不属于ProtoA ProtoB ProtoC的数据,也可以是脏数据。甚至对于buff而言,有可能会在末尾的ProtoC中,ProtoC被截断,ProtoC被截成两段,前一段位于buff中,而后一段位于下次的buff中。

如果我们采用ETestDEV中默认提供的解包函数来做的话,要实现对上述解包需求的响应,在调用unpack(prot,buff,is_auto_shift)进行解包处理时,就不得不使用is_auto_shift=false,不移除掉已经解包的buff,否则对于:

ProtoC XX XX ProtoA XX XX ProtoB ProtoA XX XX ProtoB XX XX Pro

其中,Pro表示只有协议的一半,这样的buff数据,先解包ProtoA后,如果采用is_auto_shift=true,则必然解包ProtoA时,就把之前的ProtoC XX XX从缓冲区中移除,使得ProtoC不再能被解出,则不得不使用is_auto_shift=false调用对ProtoA的解包。

整个解包程序如下:

--剩余的字节数

local leftlen={}

while true do

local res1 = unpack(protocols.ProtoA, buff.value, true)

local res2 = unpack(protocols ProtoB, buff.value, true)

local res3 = unpack(protocols.ProtoC, buff.value, true)

if res1 ~= nil and res1.value ~= nil then

table.insert(leftlen,res1.left)

ProtoA =res1.value

end

if res2 ~= nil and res2.value ~= nil then

table.insert(leftlen,res2.left)

ProtoB=res2.value

end

if res3 ~= nil and res3.value ~= nil then

table.insert(leftlen,res3.left)

ProtoC=res3.value

end

if res1 == nil and res2 == nil and res3 == nil then

break

end

end

local len=min(leftlen)

if len>=0 then

ebuff.shift_len(buff.value,#buff.value-len)

end

上述解包程序的基本思路是在一个循环中对ProtoA ProtoB ProtoC分别进行解包,解包时切记不能移除已解出的buff,然后每一次解包成功时将留下的left字节数加入到一个table中,这个table为leftlen,然后在循环中当ProtoA ProtoB ProtoC均不再能解出时,跳出循环。

循环跳出后,然后移除掉已经完成解包的所有数据,但需要留下最后留下的len,以等待下一个on_buff_recv的到来,这样下一个on_buff_recv到来时还能有机会把之前留下的len与新到来数据的组合形成完整的ProtoC。

2.3 递归解包的实现

采用2.2的方法进行解包的一个明显问题是:由于每次调用unpack进行解包处理时,并没有移除掉已经解包完成的buff,循环中的下一个调用unpack则不得不从buff的开头重新搜索解包,这显然降低了处理的速度和效率。对于如:

ProtoC XX XX ProtoA XX XX ProtoB ProtoA XX XX ProtoB XX XX Pro

这样的buff数据,我们解出了ProtoA后,自然就希望在之前的ProtoC XX XX中继续找剩余的ProtoB和ProtoC,在之后的XX XX ProtoB ProtoA XX XX ProtoB XX XX Pro继续找ProtoA、ProtoB、ProtoC。我们将ProtoA、ProtoB、ProtoC放入到1个table中,每次都取第1个Proto从buff中解包,然后把buff一截两端为buff0与buff1,重新形成一个table,设为table2,从table2中移除掉第1个,然后再在buff0中寻找table2中所有Proto的匹配,而在buff1中寻找所有table中的所有Proto。

根据上述思路,我们编写出buff中具有多种类型,每种类型具有多个包的递归解包函数,如下图所示:

专家笔记:关于ETestDEV测试脚本开发模式的考虑

2.4 递归后的测试程序开发模式

有了上述递归解包函数后,我们将这个递归解包函数放入ETestDEV公共库中,作为ETestDEV所沉淀的测试程序资产,不断壮大ETestDEV的能力,为测试程序开发提供更多的支持。

专家笔记:关于ETestDEV测试脚本开发模式的考虑

进入公共库中的公用模块OnRcvFromChannel可以在测试程序中进行应用,以简化测试程序的开发逻辑,OnRcvFromChannel引入了请求解包服务注册的机制,可以向公共库模块OnRcvFromChannel进行协议以及协议处理函数的注册,原型如下:

function OnRcvFromChannel.RegisterProto(Proto,fun)

其中Proto为在ETestDEV中定义的协议,fun是当解析到具有该协议包的数据时,协议包的处理函数,可以在这个处理函数中对接收到已经解包完成的协议包进行各种处理,如计算、界面显示、通信响应等。

3在实际项目中的实验

3.1 被测系统UUT模拟

在如下的测试环境拓扑中,被测件UUT连接着两个测试设备,当已经编写完成与协议、测试逻辑相关的测试程序与测试用例后,一个问题是如何在缺少被测件UUT的情况下,对已经编写完成的测试程序进行调试。

专家笔记:关于ETestDEV测试脚本开发模式的考虑

在ETest中不仅提供了测试程序的开发能力,也提供了模拟被测件的能力,我们只需要设置某个测试程序为模拟脚本,就可以执行这个模拟脚本作为被测件的模拟,从而驱动对测试程序的调试工作。

模拟被测件以一个单独控制台的形式运行,其运行在模拟的ETestDEV执行器上,为了确保模拟被测件根据测试程序进行响应,模拟被测件在接到测试程序发送的数据后,向测试程序发送数据包,以检验测试程序在打包解包处理中的正确性。

专家笔记:关于ETestDEV测试脚本开发模式的考虑

3.2 被测件UUT发包情况

在被测件UUT接收到测试程序发送的数据后,其一次性地向外输出多包带有不同协议的数据包,其中还夹杂着一些随机的干扰数据。如下图所示:

专家笔记:关于ETestDEV测试脚本开发模式的考虑

被测件发送出这样的数据,不同种类协议出现的顺序有可能是不同的,解包程序需要从buffer中解包出所有已在OnRcvFromChannel中注册的协议包数据。被测件对外发送的数据包模拟程序如下图。

专家笔记:关于ETestDEV测试脚本开发模式的考虑

被测件发送数据包协议的不确定性大大增加了从buffer中解包处理的难度,不仅要求逻辑正确,还必须保证性能。

3.3 实验结果

在测试程序中,我们首先向引入的公共库OnRcvFromChannel注册三种类型的协议帧格式及其响应处理函数,然后向被测件UUT发送一个“开始处理”的字符串,被测件就会从模拟程序中接收到。在ETestDEV中对于所有的类型的通道均可以使用虚拟的方式模拟各种类型接口通道的通信。

专家笔记:关于ETestDEV测试脚本开发模式的考虑

下图展示出了被测件UUT发送数据的情况和测试程序解包处理的情况,从图中可以看出,被测件发送出的1A 1A 1B 1C,ETestDEV的框架机制产生了on_buff_recv回调,在这个回调中,显然找不到在OnRcvFromChannel中注册的任何协议,所以很快跳出了递归。

专家笔记:关于ETestDEV测试脚本开发模式的考虑

紧接着被测件UUT又连续发送了多包具有协议内容的数据,注意ETestDEV里对于被测件UUT多包发送数据,只产生了一个回调,这大幅减轻了ETestDEV框架中协议处理的效率,在框架所提供的回调中,我们采用了多包解析的方式进行处理。从图中可以看到,解包时间可以在300us内完成,说明ETestDEV对于1ms周期内解包响应是满足要求的。

4启示

ETestDEV在协议包的定义、解析处理、打包等功能上,提供了校验函数扩展、字段打包解包函数扩展等功能,ETestDEV为我们提供的测试系统开发的基础框架,解决了协议、通道、测试环境、脚本执行等一系列问题。在这个框架中,我们还可以不断为其赋予新的功能,不断增长ETestDEV的能力,使得ETestDEV成为真正的测试系统生产力工具。

还想了解更多内容,欢迎关注微信公众号【ETest】

继续阅读