接口测试中,实际组包和解包过程中往往需要对不同数据类型做转换,比如:bytes []到unint32 [],所以常用的测试流程是:打包-->发包-->收包-->解包-->判断。
Python中要打包和解包数据流有多种方案,这次我们介绍如何用struct模块来完成字节流的打包和解包工作,struct模块的pack和unpack函数针对字节的大小端转换以及不同数据类型的转换也极为方便,且struct模块来源于C,这点也可以让Python与C/C++的数据类型与结构保持互通。
在讲解实例前,我们先了解下pack和unpack函数的定义。
-
pack()
按照给定的格式(fmt),把数据封装成字节流string。
struct.pack(fmt, v1, v2,…)
将v1,v2等参数的值进行一层包装,包装的方法由fmt指定。被包装的参数必须严格符合fmt。最后返回一个包装后的字符串(字节流)。 -
unpack()
按照给定的格式(fmt)解析字节流string,返回解析出来的元组tuple
struct.unpack(fmt, string)
顾名思义,解包。好比pack打包,而后就能够用unpack解包了。返回一个由解包数据(string)获得的一个元组(tuple), 即便仅有一个数据也会被解包成元组。其中len(string) 必须等于 calcsize(fmt),这里面涉及到了一个calcsize函数。struct.calcsize(fmt):这个就是用来计算fmt格式所描述的结构的大小(主要用于结构体内的字节对齐)。
pack和unpack函数的核心参数,其实是fmt,所以我们简单了解下fmt的组成,通常fmt的第一个字符代表字节顺序(可选项),也就是我们说的大小端。有以下类型可选:
image.png
如果,第一个字符是@或者不是上表中的任意一个字符,程序会采用原生字节顺序,原生字节顺序是big-endian还是little-endian,取决于主机系统。例如,Intel x86和AMD64(x86-64)是little-endian ;摩托罗拉68000和PowerPC G5是big-endian; ARM和英特尔的Itanium特性是可切换的(双endian)。可以使用sys.byteorder指令来检查你系统是按照什么排序的。
p.s. 我们使用电脑的CPU大多为Intel x86和AMD64(x86-64)架构,所以默认的字节序为little-endian。
fmt中剩余的内容遵循以下格式定义。
image.png
具体fmt的定义和使用原则,可以参考以下文章:
Python Struct fmt 结构体内存对齐 二进制文件解析_weixin_42309323的博客-CSDN博客
上面的表格比较生硬,我们还是通过一个接口测试的实例来解释pack,unpack及fmt参数的定义方法,例子如下:
-
假设接口实际对应的数据内容为11 22 33 44 55 66 77 88(byte类型 大端字节序,从左到右)。
-
在数据传输过程中,接口事件的payload定义为一个 unit32类型的数组(大端字节序),形式如下:
0x11 22 33 44, 0x55 66 77 88 或 十进制表示为(287454020,1432778632) -
所以,在数据发送过程中,我们需要将11 22 33 44 55 66 77 88 转换为287454020,1432778632后再进行发送。
同理,在数据接收过程中,我们需要将287454020,1432778632转换为11 22 33 44 55 66 77 88 后,再进行判断。
接着,我们通过struct模块的pack和unpack函数,来完成步骤3的转换。
import struct
import sys
#打印当前系统的默认字节序
print(sys.byteorder)
#建立一个测试变量,包含一个0x11-0x88的字节流,假设是原始车载总线上已配置的数据
byte_source=b'\x11\x22\x33\x44\x55\x66\x77\x88'
# 通过unpack函数解包,fmt为 !II,
# 其中!代表大端字节序,
# II代表两个unsigned Int型数据 4bytes,组合在一起)
unpack_data = struct.unpack("!II", byte_source)
print(unpack_data) # 输出(287454020, 1432778632) 假设是我们需要通过接口发送的参数
#如果list内的顺序与预期相反,则可以反转下 unpack_data = unpack_data[: :-1]
#第二部分,在发送过程中,我们将两个unsigned Int型数据,组包成一个字节流
pack_data = struct.pack("!II", *unpack_data)
print(pack_data.hex().upper() ) #以hex形式,打印组包后的字节流
#1122334455667788
假设,有些接口定义的字节序为小端字节序,我们只需要将fmt从 "!II" 改变为 "<II" 或 "II"即可。
总结,在做接口测试之初,由于Python的方案多样,我并没有使用struct函数来做字节流的解包和组包。但随着项目推进中,接口定义的数据格式和字节序需求的持续变更,我慢慢发现自建的组包解包函数已经很难满足所有的需求变更了,且自建函数内部逻辑复杂,效率低下。
后来慢慢转为struct后,函数的结构慢慢变得简洁且清晰,处理速度也得到了提升。最关键的是,无论需求如何变更,struct都能很快的应对,因为fmt的灵活和可配置性。
网友评论