Testbed单元测试实战:配置、用例与覆盖率闭环避坑
简介:《Testbed单元测试方法手册》面向使用Testbed开展C/C++白盒测试与单元测试的开发者、测试工程师及学习者,聚焦从环境配置到用例执行、覆盖率查看和报告归档的完整操作链路。压缩包内为1个doc文档,约2.01MB,按章节梳理选择待测文件与头文件路径、宏添加、分析项选择、测试序列创建、全局变量赋值、输入输出变量创建、入参指针或数组映射、桩函数返回值设置、运行测试用例、覆盖率查看及报告保存等关键环节。文中还给出错误信息、测试日志和软件版本排查等常见问题处理思路,并说明覆盖率报告与回归报告的查看方式,便于读者按目录快速定位步骤、对照工具完成单元测试实践。已有6123人学习,适合需要系统掌握Testbed单元测试流程或查漏补缺的读者参考。
1. Testbed 单元测试 白盒测试最容易卡在“用例能跑、覆盖率不闭环”
Testbed 单元测试 白盒测试最容易卡在“用例能跑、覆盖率不闭环”:编译通过,Run 也点了,覆盖率却总在 92% 上下晃,改两版测试序列还是找不到缺口。
这类现场问题靠界面按钮试不出来,得有一份能对照字段的操作手册。这份 Testbed 单元测试方法手册以 V8.2.0 为基线,把选文件、配头文件、加宏、建序列、赋全局变量、映射指针数组、设桩函数、跑用例、看覆盖率和回归报告串成一条线。
适合刚接手单元测试执行的人,也适合要把覆盖率证据交出去、又不想在配置环节反复翻车的老手。
2. 环境与工程配置:文件、头文件路径、宏三件事先做对
Testbed 的分析结果不是凭空来的,它先按你给的搜索路径和宏做预处理,再生成分析库。路径少一层、宏少一个,后面用例可能全 PASS,覆盖率却对不上真实分支。我一般把配置当成第一道验收:待测文件、头文件、宏、分析项四项都确认,再进单元测试界面。这一步没做顺,后面调用例就像在黑匣子里猜。
2.1 选择待测 C 文件与确认编译单元
先在File/Select File里选待测的.c文件,不要把头文件当待测对象选进来。选完之后别急着点分析,先确认这个文件在实际工程里依赖哪些头文件、编译时开了哪些宏。常见翻车点是:单文件能打开,但工程里的 include 顺序和宏开关跟工具里配的不一致,分析阶段就会报类型找不到,或者把某些分支当成不存在。
我一般按下面几项过一遍:
| 检查项 | 目的 | 不合格时的表现 |
|---|---|---|
待测.c文件 | 确认分析对象是编译单元 | 选成.h后函数列表为空 |
| 实际 include 路径 | 让工具找到类型和宏定义 | 报 unknown type、undeclared |
| 编译宏 | 打开条件编译分支 | 覆盖率缺分支,代码行显示不全 |
| 同目录同名头文件 | 避免引用错版本 | 分析结果和工程编译结果不一致 |
如果工程里同一个头文件名出现在多个目录,先把实际编译用的那一份路径记下来,后面配搜索路径时优先放它。Testbed 不会替你判断哪份头文件才是真的,路径顺序和 dat 文件内容写错,它只会按错的走。
2.2 头文件搜索路径:Search Options 和 dat 文件手编两条路
头文件路径配置在configure/static options里。第一次添加头文件时,先创建一个 dat 文件,存放目录按个人习惯来,别临时丢在桌面。创建后点弹框中间的Search Options,再点路径栏右侧的...选头文件目录;如果下级子目录多,勾选Add Subdirectories as search paths,然后点Add search path,最后确定。
另一条路是直接编辑 dat 文件。点右侧的Edit,在文件开头按行写目录:行首1表示这条语句有效,0表示注释。新创建的 dat 文件里可能没有有效语句,看起来路径写了,实际全被注释掉,分析时照样找不到头文件。这个坑很常见,尤其是从别人机器上拷来的 dat 文件,行首状态一定要看。
| dat 行首 | 含义 | 常见误判 |
|---|---|---|
1 | 该搜索路径生效 | 以为只是编号,随手改成 0 |
0 | 该行被注释 | 路径明明在文件里,分析却不认 |
注意:dat 文件行首的
0不是空行标记,是注释。路径看起来在,实际没生效。
路径配完后,先跑一次分析看日志里还有没有找不到的头文件。如果某个类型定义在子目录里,优先勾选子目录搜索,不要靠逐个手工补路径,后面维护成本太高。
2.3 宏定义配置:把“工具看不见的分支”打开
宏的配置入口和头文件类似,点Macros创建或打开一个 dat 文件,点Edit Default进行编辑,把需要开放的宏复制粘贴进去。这里的关键不是“宏越多越好”,而是跟实际编译保持一致。#ifdef包住的分支,如果宏没开,工具分析时根本看不到;覆盖率报告上看起来少了几行,实际是预处理阶段就被裁掉了。
我一般会把宏分成三类看:
| 宏状态 | 分析结果 | 风险 |
|---|---|---|
| 与实际编译一致 | 分支和代码行完整 | 覆盖率可信 |
| 少开宏 | 条件编译分支缺失 | 覆盖率虚高或代码行对不上 |
| 多开调试宏 | 非交付分支被算进来 | 覆盖率被稀释,报告失真 |
调试宏要不要开,取决于这次单元测试的目标。只验证交付分支,就不要把调试专用宏全打开;否则用例写不完,覆盖率还上不去。反过来,如果某个宏控制的是错误处理分支,而测试计划要求覆盖异常路径,那就必须打开,并为它设计输入。
宏改完不要沿用旧分析结果,重新跑一次分析。分析库和 workfiles 会保留上一次的痕迹,尤其是你刚改完路径又改宏的时候,旧结果混进来最容易让人误判。
2.4 分析项与 UnitTest only 的取舍
在select analysis界面选择前四项,点start analysis,等分析完成。分析完成后点按钮进入单元测试界面。如果前面没做分析,直接点UnitTest only,工具默认也会做前四项分析,效果一样,但少了单独检查分析日志的机会。
| 入口 | 适用场景 | 注意 |
|---|---|---|
| 先选分析项再 start analysis | 首次配置、路径或宏刚改过 | 先看分析日志,排掉类型和头文件问题 |
| UnitTest only | 分析库已稳定,只想快速进用例 | 出问题时仍要回来看分析日志 |
提示:改了头文件路径或宏之后,不要沿用旧分析结果,重新跑一次分析。分析和用例不是两条线,前面的配置会直接影响后面的覆盖率。
配置阶段做扎实,后面建序列时函数列表、变量类型和分支信息才是完整的。很多人把时间花在反复改用例,实际根因在头文件 dat 文件行首那个0,或者少写了一个编译宏。
3. 从函数到测试序列:建用例、赋全局变量、映射指针数组
这一段最容易出“点得出来但不可信”的用例。界面会自动生成变量,自动生成不等于正确;Input parameter与Input global混用、输出变量没设Compare Write、指针没映射,最后报告里全是 PASS,实际没验证到逻辑。单元测试的质量,八成看这里的变量语义有没有对齐。
3.1 创建测试序列与命名习惯
在单元测试界面点Sequence/New,填写序列名称。手册里建议用待测函数名,方便回归时快速定位;我一般再加场景后缀和序号,比如函数名_空指针_01、函数名_正常分支_02。同一个函数用例多了以后,回归报告里一排test1、new根本分不清是谁。
首次安装工具后做单元测试,弹框里有些选项要勾选,按手册图示勾上再点continue。进入界面后,在右侧选择待测函数,右键点Create New Test Case,弹窗后连续点continue,直到生成测试用例。生成出来的用例只是骨架,变量值、桩函数、预期输出都要自己填。
注意:序列名不要用“test1”“new”这类词。回归报告里同名用例一多,定位成本比建用例还高。
3.2 Input parameter 与 Input global 的边界
界面右下角会自动生成需要输入和输出的变量。输出变量如果value是suspend,可以直接右键选中后remove,不要让它挂着。suspend不是有效输出,回归报告里的Suspends列会把它统计进去,交报告时很难解释。
输入变量分两种:Input parameter applied through local是函数入参,只能在这个参数的Value处赋值或映射;Input global是函数里引用到的其他全局变量,可以在右下角赋值,也可以在左下角用例处右键Modify Existing Test Case/Code Inserts/Initialisation Code,在弹框里做初始化。两条路都能走,但不要同时走。
| Use 类型 | 对应对象 | 赋值位置 | 常见错误 |
|---|---|---|---|
| Input parameter applied through local | 函数入参 | 右下角该参数的 Value | 拿到 Code Inserts 里赋,驱动生成后仍取默认值 |
| Input global | 函数内引用的全局变量 | 右下角或 Code Inserts 初始化代码 | 两处都赋值,自动生成的 Input global 没删,实际生效值不确定 |
| Output global | 输出全局变量 | Regression Analysis 选 Compare Write | 选成 Assign,只写不比较,PASS 不可信 |
如果右下角已经自动生成了Input global的赋值,而你又要用Code Inserts里的赋值,先把右下角自动生成的那条删掉。两处同时存在时,工具按哪一处生效要看生成顺序,最后结果不稳定,排查起来很费时间。
3.3 Create IO Variable 的字段怎么填
右下角空白处右键,点Create IO Variable In Test Case。Name填变量名,Type填类型,Use下拉选择Input Global或Output Global。如果是Input Global,Regression Analysis选Assign;如果是Output Global,选Compare Write,然后在Value里填输入值或预期输出值。
| 字段 | 怎么填 | 作用 |
|---|---|---|
| Name | 与被测代码里的变量名一致 | 驱动生成时能对上符号 |
| Type | 先用实际类型,工具不认时退回基本类型 | 避免声明不识别 |
| Use | Input Global 或 Output Global | 决定变量参与输入还是输出比较 |
| Regression Analysis | 输入选 Assign,输出选 Compare Write | 决定工具是注入还是比较 |
| Value | 输入值或预期输出值 | 没有基准就没有比较 |
输出变量选成Assign是最隐蔽的坑:用例会 PASS,Fails也是 0,但工具只是把实际输出写进去,没有和预期值比较。报告看起来干净,实际没验证输出。只要这个变量承担验证职责,Compare Write和预期值一个都不能少。
3.4 指针/数组成员映射:Point to global Entity 与 Create User Global
函数入参是指针或数组时,不能只在Value里填个数字,要把它映射成同类型的全局变量。方法一是在指针或数组的Value处右键,点Point to global Entity或Assign to global,弹框里New Name可以任意填,类型会自动生成。映射后如果构建报变量未声明,多半是 Testbed 不识别项目里的自定义类型,这时在Declaration Type填原指针或数组的基本类型,Array Bounds填数组脚标大小,Initial Value填初值。
方法二是点菜单栏Global variables/Create User Global,弹框没有默认类型,全部手动填。创建完成后,在需要映射的指针Value处填&变量名,数组填变量名[n]。映射出来的全局变量还要单独赋值,不然指针指向的是一块没初始化的内存,用例结果不可信。
| 字段 | 含义 | 填错后的现象 |
|---|---|---|
| New Name | 映射后的全局变量名 | 重名后驱动里符号冲突 |
| Declaration Type | 声明类型,工具不认 typedef 时填基本类型 | 构建报 undeclared |
| Array Bounds | 数组脚标大小 | 数组越界或只映射到一个元素 |
| Initial Value | 指针/数组初值 | 用例读到随机值,结果飘 |
指针常用Point to global Entity,数组或需要整体赋值时用Assign to global;不管哪种,映射出来的变量都要赋初值。映射完成后先Build Driver,不要直接Execute。变量未声明、类型不匹配这类问题在构建阶段暴露最省时间,等到执行阶段再看日志,信息会绕很多。
提示:映射后先 Build Driver,再 Execute。构建阶段能挡掉大部分变量声明和类型问题。
4. 桩函数、单用例运行与覆盖率闭环:把 PASS 变成可复查证据
桩函数、运行顺序、覆盖率清除,是单元测试报告能不能复查的三道关。用例 PASS 只说明这一次比较通过,不说明覆盖率真实、不说明输出都被比较。把这三步做成固定动作,报告才敢交给别人看。
4.1 桩函数返回值:隔离外部依赖
在界面标记处双击,可以看到被测函数调用到的其他函数。如果某个函数需要设置返回值,右键选中它,点Set Return Value on Exit,在弹框的Value处填返回值。桩函数的作用是把外部依赖切开,让当前函数的逻辑单独受控,返回值要跟被测函数的分支判断对得上。
比如被测函数根据桩函数返回0或非0走不同分支,那至少要为这两种情况各设一组桩返回值。只设一个固定值,另一条分支永远进不去,分支覆盖率会卡住。返回值类型也要和函数声明一致,指针返回值不要填普通整数,否则构建阶段就会报类型错误。
Set Return Value on Exit管的是返回值。如果桩函数通过出参写值,这个入口管不到,常见做法是用全局变量或初始化代码补上出参的初值。这个边界要先看清,不然会误以为桩函数没生效。
4.2 运行用例的两种路径与 Resequence 的用途
第一个用例可以右键空白处点Run Test Case Driver,也可以点界面上方按钮;更稳的方式是按顺序点Generate Driver -> Build Driver -> Execute Driver -> ProcessRegression Driver Output。分步点的好处是能看出问题出在生成、编译、执行还是结果处理,直接一键跑到底,报错时只剩一个黑匣子。
第一个用例跑过之后,后面设计了多个用例,可以继续用上面的方法,也可以右键选中某个测试用例点Resequence This Test Case单独运行,或者点Resequence This and Subsequent Test Cases对选中的用例及之后的用例逐个运行。后者适合改了一批用例后批量重跑,比较预期输出和实际输出。
| 操作 | 执行范围 | 适用场景 |
|---|---|---|
| Run Test Case Driver | 当前用例/驱动 | 第一个用例,先看整体能不能跑 |
| Generate/Build/Execute/Process | 分步执行 | 排查构建错误或结果处理错误 |
| Resequence This Test Case | 选中的单个用例 | 只改了一个用例,快速验证 |
| Resequence This and Subsequent | 选中及之后所有用例 | 批量重跑,比较预期与实际 |
注意:用 Resequence 单独跑用例后,覆盖率报告中间列只反映当前或最后一个单独执行的用例,不要拿它当全量覆盖率。
4.3 覆盖率清除与复跑:防“假 100%”
用例通过后,右上角能看到函数覆盖率,也可以点Results/Dynamic Coverage Analysis Report看语句、分支、MC/DC 的详细情况。如果覆盖率全都满足 100%,先别高兴,点Source菜单里的Delete Testbed workfiles,在弹框里选中对应选项后点 OK,把覆盖率清掉,再按 4.2 的方法一全量跑一遍。
这一步看起来玄学,实际很有必要。workfiles 会保留之前的执行痕迹,人工操作也可能造成“看起来覆盖了”的假象。删掉后重跑,能确认覆盖率不是旧数据堆出来的。尤其是接手别人做了一半的序列,旧 workfiles 和新用例混在一起,报告很容易骗人。
注意:删 workfiles 会清掉当前工作文件,导出的 TCF 和报告先保存,再清理。
4.4 报告导出:TCF、覆盖率、回归三份文件
报告保存分三处:Sequence/Export Test Cases (TCF)点save as保存 TCF;Results/Dynamic Coverage Analysis Report点save as保存覆盖率报告;Results/Regression Report点save as保存回归报告。三份文件各管一段,缺一份,后面复查就要重新跑。
| 报告 | 菜单 | 用途 | 保存动作 |
|---|---|---|---|
| TCF | Sequence/Export Test Cases | 测试序列备份,便于复查 | Save as |
| 覆盖率报告 | Results/Dynamic Coverage Analysis Report | 语句、分支、MC/DC 证据 | Save as |
| 回归报告 | Results/Regression Report | PASS、Fails、Suspends 结果 | Save as |
文件命名我一般用“模块_函数_日期_版本”,不要只写report。同一轮测试可能改几次用例,报告名不带版本,后面根本分不清哪份是最终证据。
提示:先导 TCF,再导覆盖率和回归;清理 workfiles 前导出,避免复跑后旧报告丢失。
5. 避坑排查:五个现场问题从现象到解决
下面五个问题在 Testbed 单元测试里出现频率很高,现象看起来都像工具坏了,实际多半是配置或变量语义没对齐。按现象、原因、解决顺序排查,比反复重装工具快。
5.1 头文件路径加了,分析仍报类型找不到
现象:分析日志报 unknown type 或 undeclared,头文件路径在界面里明明有。
原因:dat 文件行首是0,路径被注释;只加了根目录,没勾Add Subdirectories as search paths;宏没配对,条件编译把类型定义屏蔽了。
解决:打开 dat 看行首1/0,改成1;勾选子目录搜索后重新Add search path;把编译宏补进 Macros dat;重新跑分析。如果还报,先Delete Testbed workfiles再分析,避免旧分析库残留。
5.2 指针映射后 Build Driver 报变量未声明
现象:映射完成,生成驱动时报 undeclared identifier。
原因:Testbed 不识别项目自定义
本文还有配套的精品资源,点击获取