加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

在C中单元测试二进制格式阅读器

发布时间:2020-12-16 07:29:23 所属栏目:百科 来源:网络整理
导读:我正在编写一个读取二进制文件格式的C库.我不控制二进制格式;它是由专有数据采集程序生成的,相对复杂.由于这是我第一次尝试进行C编程和二进制文件解析,我在确定如何构建测试和可移植性代码时遇到了一些麻烦. 出于测试目的,我认为最简单的操作是构建库以读取
我正在编写一个读取二进制文件格式的C库.我不控制二进制格式;它是由专有数据采集程序生成的,相对复杂.由于这是我第一次尝试进行C编程和二进制文件解析,我在确定如何构建测试和可移植性代码时遇到了一些麻烦.

出于测试目的,我认为最简单的操作是构建库以读取任意字节流.但我最终实现了一种封装流类型(memstream,filestream等)的流数据类型.该接口具有类似stream_read_uint8的功能,因此客户端代码不必知道字节来自何处.我的测试是针对memstream的,而文件流的东西基本上只是FILE *和fread等的包装器.

从OOP的角度来看,我认为这是一个合理的设计.但是,我感觉我正在将错误的范例填充到语言中,最终导致过度抽象,过于复杂的代码.

所以我的问题是:在保留自动化测试的同时,是否有更简单,更惯用的方式在普通C中进行二进制格式读取?

注意:我意识到FILE *本质上是一个抽象流接口.但是内存流(fmemopen)的实现是非标准的,我希望标准C具有可移植性.

解决方法

您所描述的是低级I / O功能.由于fmemopen()不是100%可移植的(在 Linux之外,它吱吱作响,我怀疑),然后你需要为自己提供一些便携式的东西,你写的足够接近,你可以在必要时使用你的代理功能(仅限)并使用尽可能的原生功能.当然,即使在您的原生栖息地,您也应该能够强制使用您的功能,以便您可以测试您的代码.

可以使用已知数据测试此代码,以确保您获取输入流中的所有字符并可以忠实地返回它们.如果原始数据在特定的字节序中,您可以确保您的“较大”类型 – 假设,诸如stream_read-uint2(),stream_read_uint4(),stream_read_string()等函数 – 都表现得恰当.对于这个阶段,您实际上并不需要实际数据;您可以制作适合自己和测试的数据.

一旦你有了这个,你还需要编写代码来读取更大类型的数据,并确保这些更高级别的功能实际上可以准确地解释二进制数据并调用适当的操作.为此,您最终需要提供格式的示例;直到这个阶段,你可能会得到你制造的数据.但是一旦你正在阅读实际的文件,你需要有一些工作的例子.或者你必须尽可能地从你的理解和测试中制造它们.这有多容易取决于二进制格式的清晰记录.

其中一个关键的测试和调试工具是规范的“转储”功能,可以为您提供数据.我使用的方案是:

extern void dump_XyzType(FILE *fp,const char *tag,const XyzType *data);

溪流是不言而喻的;通常它是stderr,但通过使其成为参数,您可以将数据获取到任何打开的文件.标签包含在打印的信息中;识别呼叫的位置应该是唯一的.最后一个参数是指向数据类型的指针.你可以分析和打印它.您应该借此机会断言您能想到的所有有效性检查,以避免出现问题.

你可以使用const char * file,int line,const char * func扩展接口,并安排在调用中添加__FILE __,__LINE__和__func__.我从来不需要它,但如果我这样做,我会使用:

#define DUMP_XyzType(fp,tag,data) 
        dump_XyzType(fp,data,__FILE__,__LINE__,__func__)

作为一个例子,我处理类型DATETIME,所以我有一个函数

extern void dump_datetime(FILE *fp,const ifx_dtime_t *dp);

我本周使用的其中一个测试可以被说服转储日期时间值,它给出了:

DATETIME: Input value -- address 0x7FFF2F27CAF0
Qualifier: 3594 -- type DATETIME YEAR TO SECOND
DECIMAL: +20120913212219 -- address 0x7FFF2F27CAF2
E:   +7,S = 1 (+),N =  7,M = 20 12 09 13 21 22 19

您可能会或可能不会在那里看到值2012-09-13 21:22:19.有趣的是,这个函数本身调用了系列中的另一个函数dump_decimal()来打印出十进制值.一年,我将升级限定符打印以包含十六进制版本,这更容易阅读(3594是0x0E0A,这是知道的14位数(E)容易理解,从YEAR开始(第二个) 0)到秒??(A),从十进制版本肯定不那么明显.当然,信息是类型字符串:DATETIME YEAR TO SECOND.(十进制格式对于局外人来说有点难以理解,但很清楚知道有指数(E),符号(S),数字(厘米)(N = 7)和实际数字(M = …)的内部人员.是的,十进制名称是严格的一个误称,因为它使用基数为100或厘米的表示.)

默认情况下,测试不会产生这样的细节级别,但我只需要使用足够高的调试级别(通过命令行选项)运行它.我认为这是另一个有价值的特征.

运行测试的最安静方式产生:

test.bigintcvasc.......PASS (phases: 4 of 4 run,4 pass,0 fail)(tests: 92 run,89 pass,3 fail,3 expected failures)
test.deccvasc..........PASS (phases: 4 of 4 run,0 fail)(tests: 60 run,60 pass,0 fail)
test.decround..........PASS (phases: 1 of 1 run,1 pass,0 fail)(tests: 89 run,0 fail)
test.dtcvasc...........PASS (phases: 25 of 25 run,25 pass,0 fail)(tests: 97 run,97 pass,0 fail)
test.interval..........PASS (phases: 15 of 15 run,15 pass,0 fail)(tests: 178 run,178 pass,0 fail)
test.intofmtasc........PASS (phases: 2 of 2 run,2 pass,0 fail)(tests: 12 run,8 pass,4 fail,4 expected failures)
test.rdtaddinv.........PASS (phases: 3 of 3 run,3 pass,0 fail)(tests: 69 run,69 pass,0 fail)
test.rdtimestr.........PASS (phases: 1 of 1 run,0 fail)(tests: 16 run,16 pass,0 fail)
test.rdtsub............PASS (phases: 1 of 1 run,0 fail)(tests: 19 run,4 expected failures)

每个程序都标识自己及其状态(通过或失败)和摘要统计.我一直在寻找bug并修复一个我偶然发现的bug之外的bug,所以有一些’预期的失败’.那应该是暂时的事态;它允许我合法地声称测试全部通过.如果我想要更多细节,我可以运行任何测试,任何阶段(测试的子集有些相关,虽然’有些’实际上是任意的),并完整地看到结果,等等.如图所示,运行该组测试只需不到一秒钟.

我觉得这在有重复计算的地方很有用 – 但我必须在某些时候计算或验证每一项测试的正确答案.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读