简介:这份PDF白皮书围绕Tessy单元测试与分类树方法展开,面向嵌入式软件测试人员、开发工程师及需要提升单元测试规范性的技术团队,重点回应如何系统设计测试用例、执行单元测试并改善软件质量。压缩包仅含1个PDF文件,大小约1.87MB,便于下载后直接查阅。内容从单元测试在IEC 61508、ISO 26262、Automotive SPICE、IEC 62304等标准中的要求切入,结合TESSY工具示例说明被测单元、接口、初始用例、测试执行、故障排查与回归测试流程,并逐步讲解分类树方法的测试相关方面识别、分类、重复分类及组合表用例指定,配有示例与工具支持说明。已有4876人学习下载,适合希望把单元测试从概念落到用例设计与自动化执行的读者参考。
1. 从一条边界用例翻车说起:Tessy 单元测试能补上什么
某嵌入式项目里有个区间判断函数,集成测试跑了几轮都正常,现场却偶发把上界值当成区间内。回头补一条单元测试用例,start=5,len=2,value=7,预期返回 0,实际返回 1,问题当场暴露。Tessy 单元测试就是干这个的:围绕 C 函数自动识别接口、组织用例、驱动执行、回收覆盖率并生成报告,把这类边界错误按在软件测试的最底层,也让软件质量不再只靠集成测试兜底。它适合写嵌入式 C、要过功能安全标准、被集成测试拖慢回归节奏的团队。后面按测试对象、两种执行方式、分类树用例设计、避坑和覆盖率基线拆开讲,力求照着能复现。
2. 单元测试与模块测试:在安全标准里把测试对象定准
2.1 unit 和 module 在 C 语言里为什么最后都落到函数
安全相关嵌入式软件的标准里,模块测试和单元测试经常混着出现。IEC 61508 用 module testing,ISO 26262 用 unit testing,Automotive SPICE 说 software unit,IEC 62304 在 5.5 要求 Software Unit Implementation and Verification,DIN EN 50128 把最低层软件组件叫 module,ISO 13849 的 V 模型也要求模块测试,DO-178 则盯低层需求和软件组件验证。术语不同,落到 C 语言,测试对象基本都是一个函数。
ISO 26262 对 unit 的定义是“atomic level software component … that can be subjected to stand-alone testing”,翻成工程话就是能单独拉出来测的最小软件组件。IEC 61508 对 module 的定义是“construct that consists of procedures and/or data declarations …”,一个 module 里可能有好几个函数,但实际项目里最常见还是一个函数对应一个 module。为了避免和 C 的源文件 module 混淆,后面统一说 unit,指 C 函数。
这个定义看着像咬文嚼字,实际影响测试边界。比如一个函数只做数值转换,入参和返回值都在寄存器/栈上,隔离成本低;另一个函数读写全局状态、调外设驱动、依赖中断,硬把它当 unit 测,stub 工作量可能比函数本身还大。我一般会先看函数的接口复杂度:纯计算函数优先做单元测试,带硬件副作用和全局状态的函数先拆接口再测。标准要求的是验证单元按规格工作,不是让测试工具去硬啃一个耦合怪。
注意:标准里写“模块测试”不等于只能测整个 .c 文件。C 语言里把函数作为 unit 是通行做法,前提是接口清单列全,全局变量和被调函数都算进广义接口。
2.2 接口清单:黑盒测试的输入、输出和被调函数
单元测试按接口规格做黑盒测试,不依赖函数内部结构。接口不只是形参:函数读取的变量是输入,写入的变量是输出,返回值算结果,被调函数在广义上也属于接口。一个变量可以同时是输入和输出,比如自增计数器。测试用例的结构就是“给输入变量一组具体值,断言输出变量和返回值等于预期”。
以区间判断函数为例,接口表可以这样列:
| 变量/元素 | 方向 | 类型 | 约束 | 边界关注点 |
|---|---|---|---|---|
| start | 输入 | int | 任意整数 | 负值、0、正值 |
| len | 输入 | int | 规格要求 len>0 | 0、1、大于1、负数非法 |
| value | 输入 | int | 任意整数 | 小于 start、等于 start、区间内、等于 start+len、大于上界 |
| 返回值 | 输出 | int | 1 在区间,0 不在 | 两个分支都要有预期 |
| 被调函数 | 广义接口 | 函数 | 按规格返回/写状态 | 正常返回、错误码、副作用 |
Tessy 用源码自动识别接口,形参、全局变量、返回值一般都能扫出来。自动识别之后必须人工核对三件事:全局变量有没有漏,指针参数指向的缓冲区长度按谁算,宏和条件编译有没有把接口改掉。指针参数尤其容易翻车,工具看到的是指针地址,测试数据要按指针指向的类型和长度准备,否则用例看着执行了,实际只覆盖到前几个字节。
接口表不是给工具看的摆设,它决定用例设计的上限。输入变量列不全,分类树就少一个维度;输出断言写少了,函数改坏也发现不了。我习惯把接口表贴在测试工程目录里,后面加用例、改 stub、做回归都拿它对一遍,省得三周后自己都忘了某个全局变量是干嘛的。
2.3 什么时候值得做:复杂度、调用频次、安全关键三条筛选线
单元测试能降低测试复杂度,把整个应用里的一小块功能单独拎出来验证,也能检查错误敏感输入,不只测“正常值”。但它不是所有函数都值得同等投入。白皮书里提到用 McCabe 圈复杂度筛函数,我一般再加两条:调用频次和安全等级。三条线一起过,优先级基本就清楚了。
| 筛选线 | 观察指标 | 建议动作 |
|---|---|---|
| 复杂度 | McCabe 圈复杂度高、分支多、嵌套深 | 优先做单元测试,用例按分支和条件组合展开 |
| 调用频次 | 被多个模块调用、在中断/周期任务里频繁执行 | 优先做,回归频率也高 |
| 安全关键 | 属于安全相关单元、标准要求 SIL/ASIL 对应覆盖 | 必须做,覆盖率目标按等级定 |
| 接口纯度 | 纯计算、少全局状态、少硬件依赖 | 适合做,隔离成本低 |
| 时序交互 | 单元间时序、中断与主循环交互 | 单元测试单独做不完,交给集成测试 |
复杂度高但接口很脏的函数,常见做法是先重构再测,不然 stub 会写成另一个小系统。调用频次高的函数哪怕逻辑简单也值得测,因为一旦错了影响面大。安全关键单元没什么好商量的,标准要求单元测试和覆盖率,省这一步后面审计还得补。
单元测试也有边界。它验证不了单元之间的时序交互,比如两个任务通过队列传递数据的时序、中断和主循环的竞态,这些归集成测试。把单元测试当万能药,最后会得到一堆绿色用例和一个仍然偶发死机的系统。筛选的目的不是少测,是把测试资源放在能最早暴露缺陷的地方。
2.4 谁写用例谁执行:分工的现实折中
开发者不适合测自己的代码,这一点在单元测试里尤其明显。规格要求的功能如果开发者压根没实现,他设计用例时也很难想到去测这个缺失功能;异常输入、错误处理、边界条件,自己写代码时容易顺着自己的实现思路走。外部测试人员来测也有问题:大部分时间花在熟悉接口上,得搞清楚“绿色”这种抽象输入怎么变成具体 RGB 值,还得判断结果变量对不对。
折中做法是测试人员按规格写抽象用例和预期结果,开发者把抽象值转成具体值并执行。比如测试人员写“输入颜色=绿色,预期输出亮度等级=2”,开发者知道绿色对应 RGB(0,255,0),把它填进 Tessy 用例执行。用例失败,开发者当场能查实现,改完再跑。这个分工把“用例设计基于规格”和“执行基于实现知识”拆开了,比让开发者凭空想用例靠谱。
| 交接字段 | 测试人员填 | 开发者填 |
|---|---|---|
| 用例 ID | UT_RANGE_001 | 不改 |
| 规格来源 | 需求条目/接口文档 | 核对实现 |
| 输入抽象值 | 上界值、区间内值 | 具体 start/len/value |
| 预期输出 | 返回 0/1、错误码 | 核对断言 |
| 执行结果 | 不填 | 通过/失败/阻塞 |
| 缺陷链接 | 不填 | 缺陷单号 |
很多标准要求开发和测试组织分离,这种“测试人员设计、开发者执行”的做法不完全满足分离要求,实践中也不多见。但它至少把用例设计的规格视角保住了,比开发者自己写自己测强。团队小的时候,我一般会让另一个人做用例评审,哪怕不独立成组,也能把“漏实现的功能没人测”这个坑填掉一半。
3. 用 Tessy 跑通区间判断单元:original-binary 与 test-application 怎么选
3.1 被测单元与那个必现的边界错误
原始资料里的示例是一个整数区间判断函数:给定起点 start 和长度 len,判断 value 是否落在start <= value < start+len内,只看整数。C 实现常见写法如下:
/* 判断整数值是否落在 [start, start+len) 区间内 * start: 区间起点 * len: 区间长度,规格要求 len > 0 * value: 待判断的整数 * 返回: 1 在区间内,0 不在区间内 */ int range_contains(int start, int len, int value) { if (value >= start && value <= (start + len)) { /* 边界错误:应为 < */ return 1; } return 0; }逻辑说明:函数有两个分支,比较value和区间上下界。错误在第二个比较用了<=,当value == start + len时会被误判为在区间内。参数说明:start和value可以是任意整数,len按规格必须大于 0;长度非法时函数没有错误码,这本身就是接口设计缺口,测试时要单独标出来。
用start=5, len=2, value=7执行,预期返回 0,实际返回 1。这个用例不需要跑整个系统,也不需要等某个业务流程触发,单元测试几秒钟就能暴露。后面所有操作都围绕这个函数展开,换成自己的被测函数时,接口表和预期值照同样方式替换。
3.2 两种执行路径:原二进制测试与测试应用测试
单元测试有两种常见执行方式。原二进制测试用完整应用,编译、链接、烧录都按最终产品来,调试器在函数入口下断点,应用跑到断点后喂输入数据,单步或继续执行到函数结束,读输出变量。测试应用测试只把被测单元从原应用里拿出来,配一个测试用 main 和启动代码,被调函数用 stub 替代,编成一个专门的可执行测试应用。
| 维度
本文还有配套的精品资源,点击获取