LDRA Testbed V8.2.0 C工程白盒测试配置与单元测试实战 简介《Testbed工具手册—单元测试方法V1.0.doc》面向开发者和测试工程师聚焦C/C白盒单元测试的落地操作适合需要借助LDRA Testbed建立规范测试流程、补齐覆盖率与回归验证经验的读者。压缩包内仅含1个doc文档大小约2.01MB以图文步骤组织目录便于按章节查阅和随查随用。内容围绕Testbed V8.2.0展开涵盖待测文件选择、头文件路径与宏配置、分析项选取、测试序列创建、全局变量赋值、输入输出变量设置、入参指针或数组映射、桩函数返回值处理、用例运行、覆盖率查看及报告保存等关键环节。文档还整理了测试用例执行失败、软件崩溃等常见问题的排查思路并说明覆盖率报告与回归报告的查看方法。已有6123人学习适合希望系统掌握Testbed单元测试流程并提升测试规范性的技术人员参考。1. 装完 LDRA Testbed V8.2.0 才发现坑不在安装包在头文件和宏接手一个 C 工程做白盒测试第一次打开 LDRA Testbed V8.2.0 的人十有八九会卡在同一个地方软件明明装好了选完待测的 .c 文件点分析结果满屏都是“类型未定义”“头文件找不到”。看上去像是安装失败其实是静态分析的路径没配对。这套环境的门槛从来不在安装程序本身而在选择文件之后的头文件路径、宏定义和数据类型识别这三件事上。做单元测试、走白盒测试流程的开发者尤其是要给遗留 C 代码补覆盖率、出回归报告的基本绕不开它。配错一次后面创建测试序列、给全局变量赋值、看 MC/DC 覆盖率全是白费功夫所以这篇我按自己实际装、实际配的顺序从环境准备一路写到装完怎么验证跑通。2. 环境准备LDRA Testbed V8.2.0 的版本选择与安装前清单装 Testbed 这类静态分析与单元测试工具最怕的不是装不上而是装上了却分析不了代码。V8.2.0 这个版本号在手册里是明确写死的不要随便换成更高或更低的版本因为 Testbed 的编译器前端、解析器和对 C 标准的支持程度在不同版本之间有差异尤其是老旧工程里常见的 KR 风格声明、自定义类型 typedef、宏拼接换版本后解析行为会变。我一般会先把环境对齐再动安装包。2.1 安装前必须确认的四项环境条件装之前先把下面这张表逐项对一遍少一项后面都可能翻车。这不是形式主义Testbed 分析代码时要调用本机的编译环境做预处理环境缺一块分析阶段就会报出莫名其妙的错误。检查项要求不满足时的表现操作系统Windows 桌面版位数与安装包一致安装程序启动即退出磁盘空间预留足够空间放工具本体与工作文件分析中途写 workfiles 失败编译环境与本工程实际使用的 C 编译器一致头文件搜索不到、内置宏缺失工程源码待测 .c 与全部 .h 可读路径不含特殊字符选文件时列表为空或路径截断这里最容易被忽略的是第三项。很多人以为 Testbed 自带解析器就不需要编译器实际上它做静态分析、生成驱动、编译测试驱动时都要依赖本机的编译工具链。我一般会先在命令行里确认编译器能正常工作再打开 Testbed这样出问题时能立刻分清是工具链的锅还是 Testbed 配置的锅。# 先确认本机编译器可用版本与工程要求一致 gcc --version # 确认工程头文件目录能被正常预处理提前暴露缺失的 include gcc -E -I./include -I./src ./src/module_a.c -o /dev/null第一条命令只是确认工具链存在且版本对得上第二条才是关键用工程的真实头文件路径做一次预处理如果这一步就报找不到头文件那 Testbed 里同样会报先在命令行把 include 路径理顺比在图形界面里反复试要快得多。-I可以重复写多次顺序就是搜索顺序子目录多的时候把公共目录放前面。2.2 安装目录与工作文件的存放约定安装路径我建议全英文、无空格。Testbed 会在工作目录下生成一堆中间文件和分析结果路径里带中文或空格时某些环节写文件会失败报错信息还特别含糊属于典型的玄学问题。另外一个血泪经验不要把工作文件目录设在源码目录里面否则清理覆盖率数据时容易误删源码我第一次就差点把待测文件一起清掉。# 目录规划示例工具装在独立目录工程与工作文件分开 /opt/ldra/testbed/ # 工具安装目录纯英文无空格 /work/project_x/src/ # 待测源码 /work/project_x/include/ # 头文件 /work/project_x/testbed_wk/ # Testbed 工作文件与报告输出工作文件目录单独拎出来既方便做源码版本管理时排除也方便后面执行「清除覆盖率数据再重跑」这个动作时一刀切干净。测试序列、覆盖率报告、回归报告都往这里存回归时找文件不用满硬盘翻。2.3 首次启动后的基础设置装完之后第一次启动先别急着选文件。进 Configure 菜单把静态分析选项过一遍确认编译器类型选的是与本工程一致的那一项。Testbed 的静态分析选项里包含编译器选择、语言标准、以及头文件和宏的配置文件路径这三样决定了它怎么理解你的代码。这一步配错后面所有分析结果都不可信——覆盖率看着是 100%实际可能是把一堆没解析的代码当成了不存在。提示把这一步的配置单独存成一个配置文件并纳入版本管理换机器或换人接手时直接复用比口头交接靠谱得多。3. 配置头文件路径与宏两个 dat 文件决定分析能不能跑起来选择文件之后的第一件事就是配头文件路径和宏。Testbed 把这两类配置分别放在两个 dat 文件里头文件路径走 Configure / Static Options 下的搜索路径配置宏走 Macros 页的编辑入口。理解这两个文件的格式比记住点哪个按钮更重要因为界面操作偶尔会因为版本差异长得不一样但 dat 文件的规则是稳定的。3.1 头文件路径的两种配置方式第一种是界面方式。点 File / Select File 选好待测的 .c 文件再进 Configure / Static Options第一次配头文件时点按钮新建一个 dat 文件存放位置按个人习惯定建议就放工作文件目录。建好之后点弹框中间的 Search Options点输入框右侧的「…」选择头文件路径如果头文件分了很多层子目录勾上 Add Subdirectories as search paths让工具递归搜索再点 Add search path 确认。第二种是直接编辑 dat 文件我更推荐这种因为可复制、可备份、可对比。dat 文件里每一行是一条搜索路径行首的 1 表示该行有效0 表示被注释掉。新建的 dat 文件里没有任何有效语句需要手动把目录粘进去。# 头文件搜索路径配置文件示例行首 1 有效0 注释 1 /work/project_x/include 1 /work/project_x/include/platform 1 /work/project_x/src/common 0 /work/project_x/include/legacy_deprecated行首那个数字是开关不是行号写成 0 相当于临时屏蔽这条路径排查「某个头文件被两处同名文件抢了」的时候特别好用把可疑路径关掉重跑分析看报错有没有变化。路径建议写绝对路径相对路径在切换工作目录后会失效这种坑很难第一时间联想到配置上。如果下级子目录很多界面上的递归勾选等价于把每条子路径都写进来只是工具帮你展开路径顺序依然影响谁先被搜到。3.2 宏的添加与常见写法宏的配置和头文件类似点 Macros 页创建或打开一个 dat 文件再点 Edit Default 进入编辑把需要开放的宏一行一条粘进去。宏的用途主要有两类一是工程里靠宏做条件编译不开对应宏代码分支根本不会被分析到二是某些平台相关的宏定义了数据类型和属性不开会导致类型识别失败。# 宏配置文件示例条件编译开关与平台类型定义 PROJECT_CFG_A USE_PLATFORM_B PLATFORM_WORD_SIZE32前两条是纯粹的开关宏写上就生效不需要赋值第三条带值的宏按名称值的写法给出适用于影响类型宽度的平台宏。要注意的是宏一旦开多了也会出问题互斥的两个配置宏同时打开代码里会出现重复定义或者走错分支分析结果直接失真。我一般按工程实际的编译配置来构建脚本里-D了什么这里就写什么保持一一对应。3.3 配置完成后先跑一遍静态分析验证配完路径和宏别急着进单元测试界面。先在 select analysis 界面把前四项分析项勾上点 start analysis等它跑完。这一步是在验证配置有没有生效如果头文件和宏都配对分析会顺利结束如果还报类型未定义、文件找不到说明配置没落到位回去查 dat 文件。这个先后顺序很重要。有些人心急直接点单元测试界面的 UnitTest only 按钮它默认会补做前四项分析看起来省事但一旦配置有问题错误会混在一堆测试相关的提示里定位成本翻倍。先单独跑分析、把配置问题清干净再进测试环节这是我踩过坑之后固定下来的流程。4. 单元测试全流程从测试序列到覆盖率落地分析跑通之后才真正进入单元测试。这一段的操作链条比较长建测试序列、给全局变量赋值、创建输入输出变量、处理指针和数组映射、设置桩函数返回值、运行用例、看覆盖率、存报告。每一环都有具体的参数要填填错了用例会跑出假失败或者假通过比不跑更危险。4.1 创建测试序列与选择待测函数进单元测试界面后点菜单 Sequence / New填序列名称。我的习惯是直接用待测函数名做序列名回归时一眼能定位到是哪个函数的用例别人接手也不用猜。如果这是装完工具后第一次做单元测试弹框里有几个选项需要勾上这些是初始化相关设置勾完点 continue。然后在界面右侧找到待测函数右键点 Create New Test Case弹窗里连续点 continue直到测试用例生成。生成出来的用例是空壳输入输出都还没赋值接下来才是真正花时间的部分。4.2 全局变量赋值与输入输出变量创建界面右下角会自动列出这个函数需要的输入和输出变量。有个细节要留意自动生成的输出变量如果 value 显示为 suspend说明工具没法自动推断它的期望值直接右键选中点 remove 移除留着会让回归报告里出现一堆 Suspends 比例。输入变量一般分两种。一种是 Input parameter applied through local也就是函数的入参只能在这个位置赋值或者做映射另一种是 Input global即函数体内部用到的其他全局变量可以在右下角赋值也可以右键用例走 Modify Existing Test Case / Code Inserts / Initialisation Code在代码插入的方式里赋值。这两种方式别混用如果右下角已经自动生成了某个 Input global 的赋值而你打算用 Code Inserts 赋必须先把右下角那条删掉否则两处赋值打架实际生效的是哪个不好判断。需要新增变量时在右下角空白处右键点 Create IO Variable In Test CaseName 填变量名Type 填类型Use 下拉选 Input Global 或 Output Global。选 Input Global 时 Regression Analysis 里选 Assign选 Output Global 时选 Compare Write然后在 Value 里填预期输出值。这里 Assign 和 Compare Write 的区别是整个用例判定逻辑的核心前者是把值喂进去后者是拿实际输出和预期比选错了用例永远 PASS覆盖率再高也说明不了问题。4.3 入参指针与数组的映射处理函数的入参是指针或数组时必须先做映射把指针指向一个同类型的全局变量否则用例没法执行。方法一是在指针或数组的 Value 处右键点 Point to global Entity 或 Assign to global弹框里 New Name 随便填一个可读的名字类型会自动生成。这里有个高频翻车点映射后如果报「变量未声明」多半是 Testbed 不认识研发自定义的类型。解决办法是在 Declaration Type 里填原指针或数组类型的基本类型比如自定义类型底层是 unsigned char就填 unsigned charArray Bounds 处如果映射成数组填数组下标大小Initial Value 填初值。/* 映射示例自定义类型指针映射为基本类型数组 */ /* 原函数入参MyBufType *buf底层为 unsigned char */ /* Declaration Type: unsigned char */ /* Array Bounds: 64 */ /* Initial Value: 0 */上面三行注释对应弹框里三个字段的填法原因是 Testbed 的类型解析对研发自定义类型支持有限退回基本类型能让它正确分配内存并生成驱动。方法二是走菜单 Global variables / Create User Global 手动创建弹框一样但没有默认类型全部手填创建完再在指针的 Value 处填变量名或变量名[n]完成映射。手动创建的好处是可控坏处是容易漏字段我一般在自动映射失败时才用它。4.4 桩函数返回值设置被测函数里调用了外部函数时单元测试要把这些调用隔离掉否则测的就不是这一个单元了。在界面标记处双击可以看到函数中调用到的函数列表右键选中某个函数点 Set Return Value on Exit在 Value 里设返回值。桩函数的返回值要按被测函数的分支逻辑来设计想让某个错误分支被覆盖就把桩函数返回值设成能触发该分支的值这是把分支覆盖率和 MC/DC 覆盖率刷上去的主要手段。4.5 运行用例的两种方式与区别第一种方式针对函数的第一个用例在空白处右键点 Run Test Case Driver或者点界面上方对应按钮。也可以按 Generate Driver → Build Driver → Execute Driver → ProcessRegression Driver Output 的顺序一步一步点。我推荐后者尤其是第一次跑的时候分步执行能在生成驱动、编译驱动、执行驱动各阶段分别暴露问题比一把梭之后看一个笼统的失败提示强太多。第二种方式针对已有用例之后的批量场景右键某个用例点 Resequence This Test Case 单独重跑或者点 Resequence This and Subsequent Test Cases 把选中用例及其后所有用例逐个单独执行。这个操作的实际意义在于逐个对比预期输出和实际输出定位是哪个用例开始偏离。注意单独执行会改变覆盖率报告的呈现方式这点在下一章细说。4.6 覆盖率查看与清除重跑用例 PASS 之后右上角能直接看到函数覆盖率概况要看详细的语句、分支、MC/DC 覆盖情况点菜单 Results / Dynamic Coverage Analysis Report。如果覆盖率全部 100%先别高兴要做一次清除重跑验证点菜单 Source / Delete LDRA Testbed workfiles弹框里勾上对应选项后确定把覆盖率数据清掉再按 4.5 的第一种方式重跑一遍观察覆盖率是否仍然是 100%。这一步是防人为失误的后悔药。之前跑过的数据会残留某些语句看起来被覆盖了实际是上一次运行留下的痕迹清除后重跑还满才是真满。我见过有人拿着 100% 的报告去评审重跑一遍掉到 87%原因就是没清工作文件。报告保存三件套别漏Sequence / Export Test CasesTCF存测试用例Results / Dynamic Coverage Analysis Report 存覆盖率报告Results / Regression Report 存回归报告都是点开之后 save as。三份文件对应三类追溯需求缺一份后面补起来很麻烦。5. 避坑记录五个让单元测试白跑的常见问题下面这几条都是我或身边人真实踩过的按现象、原因、解决三段写。翻车大多不在操作本身而在配置和预期理解上。5.1 分析阶段报大量类型未定义现象选完 .c 文件点分析输出窗口刷出一片类型未定义、标识符未声明的报错代码看起来完全没法解析。原因头文件搜索路径没配全或者配了但路径写成了相对路径工作目录一变就失效也可能是工程依赖的平台类型宏没打开导致 typedef 分支整体没被解析。解决回到 Configure / Static Options 检查 dat 文件确认行首是 1 不是 0路径用绝对路径再核对待测文件实际#include了哪些目录逐条补齐最后把构建脚本里的-D宏抄进宏 dat 文件。改完先在命令行用预处理命令验证一遍再去 Testbed 里跑。5.2 用例全 PASS 但覆盖率上不去现象所有用例状态都是 PASS失败率 0但语句覆盖率卡在七八十上不去分支覆盖更低。原因桩函数返回值设计得太单一被测函数里的错误分支和边界分支从来没被触发或者用例之间没有做数据隔离前一个用例的全局变量残留影响了后一个。解决对着覆盖率报告逐条看未覆盖的分支报告里未覆盖的分支显示为蓝色反推需要什么样的桩函数返回值和入参组合补用例全局变量赋值尽量走 Code Inserts 的 Initialisation Code保证每个用例进来时状态是干净的而不是依赖上一个用例的残留。5.3 映射指针后报变量未声明现象给指针或数组入参做完映射运行时提示某个变量未声明用例执行不下去。原因Testbed 不识别研发自定义的类型名自动生成的类型声明它解析不了。解决在映射弹框里把 Declaration Type 改成该自定义类型底层的基本类型Array Bounds 填数组大小Initial Value 填初值或者用 Global variables / Create User Global 手动创建全字段手填再在 Value 处填变量名。改完重新生成驱动再跑。5.4 回归报告里出现 Suspends 比例现象回归报告里 Suspends 一列不是 0说明有输出全局变量的值还是 suspend 状态。原因自动生成的输出变量没被移除工具没法推断它的期望值就一直挂着 suspend。解决在右下角把 value 为 suspend 的输出变量右键 remove 掉需要比较的输出变量自己用 Create IO Variable In Test Case 建Use 选 Output GlobalRegression Analysis 选 Compare WriteValue 填预期值。回归报告的 Status 和 Fails 两列是重点前者必须全 PASS后者必须是 0。5.5 清除工作文件后源码目录被误伤现象执行 Delete LDRA Testbed workfiles 之后发现工作目录里少了一些不该少的文件。原因工作文件目录和源码目录混在一起清理动作按目录范围执行源码被顺手带上。解决从源头改工作文件目录独立于源码目录比如都放在/work/project_x/下但源码在src/、工作文件在testbed_wk/清理时只指向后者。这个习惯一旦养成后面做覆盖率清除重跑就很放松不用每次都先备份。6. 装完怎么验证用 MC/DC 报告和回归报告确认环境真的跑通了工具装好、分析能跑、用例能执行还不算完。真正的验证是看两份报告覆盖率报告证明用例设计到位回归报告证明执行结果可信。我一般按下面的顺序过一遍全过程十分钟左右能把环境问题和用例问题分开。先打开 Results / Dynamic Coverage Analysis Report。报告里能按函数看到语句、分支、MC/DC 三类覆盖率。点覆盖率前的函数名可以跳到报告里该函数的具体位置再细看。中间一列和右边一列的显示规则要分清这是最容易误读的地方运行方式中间一列显示内容右边一列显示内容第一种方式整批运行所有执行过的用例的语句与分支覆盖当前所有用例的累计执行情况第二种方式单独运行当前或最后一个单独执行用例的覆盖当前所有用例的累计执行情况覆盖到的语句在中间列显示为绿色或黑色初次覆盖或单独运行时中间列是绿色函数体下方看分支覆盖到的是黑色未覆盖的是蓝色。再往下是 MC/DC 部分报告会列出实际代码行号和判断条件代码用 C1、C2、C3 做代号下方表格展示这几个条件之间的逻辑关系以及所有需要覆盖的组合情况中间列和右列显示 yes 表示已覆盖no 表示未覆盖。看 MC/DC 时我的习惯是先数 no 的个数再回代码里找对应的组合。比如一个if (a b || c)的条件报告里会把它拆成若干组合某个组合是 no就说明少了一个能让该组合成立的入参或桩函数返回值组合。补完用例后一定要回到第 4.6 节的流程清除工作文件、重跑、再看报告避免看到的是旧数据。再打开 Results / Regression Report。报告开头是测试人员编写的序列描述往下是用例执行结果表。Status 一列必须全为 PASSFails 一列必须是 0Suspends 一列最好是 0如果 Status 有非 PASS要提问题单并追溯到具体用例如果 Suspends 不为 0说明还有输出全局变量挂着 suspend 没处理。表格里同时给出函数名、文件名、输入输出个数以及每个用例的描述、输入输出和桩函数详细信息追溯问题时直接从这张表往下翻。从那以后我每次配完 Testbed都强制走一遍「清除工作文件 → 重跑 → 对比覆盖率与回归报告」这套动作不管上一次跑得多漂亮。工具会骗人残留数据更会骗人只有清干净重跑出来的数字才算数。希望帮到你。本文还有配套的精品资源点击获取