
简介面向C/C开发与测试人员的LDRA Testbed单元测试操作手册聚焦白盒测试场景下的工具落地与流程规范。整包仅含1个doc文档大小约2.01MB按章节组织便于随查随用。内容围绕Testbed V8.2.0展开依次说明头文件路径配置、宏添加、分析项选择、测试序列创建、全局变量赋值、输入输出变量创建、入参指针或数组映射、桩函数返回值设置以及测试用例运行、覆盖率查看与报告保存等关键环节并补充常见问题排查、覆盖率报告和回归报告的查看方法。已有6123人学习适合需要按步骤完成单元测试配置、执行与结果复盘的工程师参考。1. 一份 V1.0 手册和 V8.2.0 安装包版本对不上后面九步全白做先说一个我见过的翻车现场有人拿到一份《Testbed 工具手册—单元测试方法 V1.0》照着里面的截图在机器上装 C/C LDRA Testbed装完发现菜单栏里Configure/Static Options下面根本没有手册里那个按钮头文件路径怎么都加不进去后面的白盒测试一个函数都跑不起来。折腾一整天最后发现是版本对不上——手册对应的是C/C LDRA Testbed V8.2.0而他装的是另一个大版本界面重组过dat配置文件的解析规则也变了。这类单元测试环境的坑几乎全在“装完之后的第一步配置”上代码分析能启动头文件却找不到宏没开函数整个被#ifdef屏蔽掉测试序列建好了指针入参一映射就报未声明。Testbed 不是那种双击安装、打开就能用的工具它要绑定编译器、绑定头文件目录、绑定宏集合任何一环没落地覆盖率报告就是空的。这篇按装前清单、工程接入、九步配置、排错、验证的顺序走一遍适合第一次搭这套环境的人照抄也适合熟手核对版本差异和参数取舍。2. 装前清单Testbed V8.2.0 的宿主、编译器与授权怎么定装之前先把四件事定下来装在什么系统上、用哪个编译器做前端解析、授权文件放哪、工作目录放哪。这四项定错后面配头文件和宏都是在沙地上盖楼。2.1 宿主环境与编译器匹配清单Testbed 做的是 C/C 的静态分析和插桩动态测试它自己不实现预处理和编译而是调用宿主上的编译器前端去解析源文件。所以宿主系统、编译器、工具版本三者要一起看不能只看系统。项目本手册对应的配置取舍说明工具版本C/C LDRA Testbed V8.2.0手册的操作路径与截图都以这个版本为准跨大版本界面和dat语法会有差异被测语言C、C 源码混合工程建议 C 与 C 分开建工程避免解析器打架编译器前端宿主上已安装并与工具绑定的 C/C 编译器必须和最终出包的编译器同族同大版本否则预处理结果对不上授权本地 license 文件或网络授权授权失效时工具会静默降级到只读分析容易误判磁盘工作目录预留足够空间插桩会生成大量中间文件工作目录小了中途写满工作目录纯英文路径无空格、无中文这是最容易忽略的一条路径带空格时驱动生成会莫名失败“同族同大版本”这句不是套话。预处理结果直接决定宏展开后的代码形态编译器版本差一个内置宏就可能差一批分析出来的函数分支数都不一样覆盖率百分比自然全歪。我一般会先在被测工程里跑一遍gcc -dM -E - /dev/null把内置宏列表存一份后面配宏的时候对着看。# 导出宿主编译器的内置宏作为配宏 dat 文件时的对照基线 gcc -dM -E - /dev/null /project/tb_work/compiler_builtin_macros.txt # 同一条命令换成 g 再导一份C 工程用得上 g -dM -E -x c - /dev/null /project/tb_work/compiler_builtin_macros_cpp.txt这两条命令不产生编译输出只让编译器把预处理完成后自己定义的所有宏打印出来。-dM表示只输出宏定义-E表示只做预处理-表示从标准输入读一个空文件。落盘之后哪些宏是编译器自带的、哪些是工程里#define的一眼就分得清配宏的时候不会把两者混进同一个dat文件里互相覆盖。有一点要注意不要在 Windows 盘符加空格的分区上建工作目录也不要放在同步盘的目录里。插桩过程中 Testbed 会高频读写工作文件同步盘会锁文件表现是驱动生成到一半卡死日志里什么都不写——这种没日志的卡死最难查。2.2 授权、安装目录与工作目录的落地位置安装目录和工作目录是两个概念很多人在同一层目录下混放结果清理工作文件的时候把工程配置一起删了。我的习惯是三分安装目录/opt/ldra/testbed只读安装完不再往里写东西。授权目录/opt/ldra/license权限收紧到只有部署账号可读。工作目录/project/tb_work每次分析的可写产物都落在这里删了能重建。环境变量按下面这样配写进部署账号的 shell profile 里不要写/etc/profile——这台机器上可能还有别人的编译环境全局改 PATH 迟早出事。# Testbed 运行环境的三个关键变量写在 ~/.bashrc 或部署账号的 profile 里 export LDRA_HOME/opt/ldra/testbed export PATH$LDRA_HOME/bin:$PATH export TB_WORK/project/tb_work # 授权文件位置按实际安装方式二选一 export LM_LICENSE_FILE/opt/ldra/license/testbed.lic # export LM_LICENSE_FILE27000license-host # 工作目录固定下来插桩中间产物全部落这里 mkdir -p $TB_WORK/log $TB_WORK/driver $TB_WORK/reportLDRA_HOME是安装根命令行入口和配置模板都在它下面PATH前置插入是为了让工具自带的脚本优先于系统同名脚本LM_LICENSE_FILE指到具体的.lic文件或者指到网络授权的端口加主机形式二选一不要两个都写。TB_WORK不是工具要求的变量是我自己加的约定好处是所有中间产物有统一出口出问题的时候直接看log子目录不用满盘找。提示授权文件路径里不要出现中文和空格也不要放在网络映射盘上。授权读取失败时部分版本不会弹窗报错而是直接以只读模式进主界面表现为“分析按钮是灰的”。2.3 安装后的第一轮自检装完先别急着打开工程做三件小事确认环境是活的。第一确认主程序能起来且标题栏或关于对话框里显示的版本是V8.2.0。版本号对不上后面所有操作路径都别照着这份手册走。第二确认工作目录可写。# 三步自检版本、目录写权限、编译器可见性 ls -l $LDRA_HOME/bin | head -20 touch $TB_WORK/.probe rm -f $TB_WORK/.probe echo workdir ok which gcc gcc --version | head -1第一条列安装目录里的可执行入口确认装全了第二条探针验证工作目录当前账号可写权限不对的话插桩会失败在第一阶段第三条确认编译器在 PATH 里可见Testbed 调编译器时不走别的路径就认宿主 PATH。第三用一个最小文件跑通一次“选文件 → 分析 → 出覆盖率”的链路不要一上来就拿正式工程试。最小文件控制在一个.c加一个.h函数里有if/else和这样语句、分支、MC/DC 三类覆盖率都有东西可看。这一步过了说明授权、编译器绑定、工作目录全部就位可以进正式工程的接入配置。3. 工程接入配置头文件路径与宏定义这两个 dat 文件怎么落地正式工程接进来第一件事不是建测试序列而是把Configure/Static Options里的头文件路径和宏配置对。这两项都是落在dat文件里的工具只认这两种文件配错了不会报“路径无效”而是分析阶段大面积的类型未定义、函数被跳过覆盖率看着有数字实际全是错的。3.1 用 Search Options 生成头文件路径 dat路径是Configure/Static Options第一次配要先点界面上的新建按钮创建一个dat文件存放位置按个人习惯我一般放在工程工作目录下命名成include_paths.dat跟工程一起进版本库——换机器、换人接手直接把这份文件带过去就行不用重新点一遍。创建完点弹框中间的Search Options里面的...按钮选头文件根目录。如果被测工程的目录层级深比如include/platform/,include/driver/这种勾上Add Subdirectories as search paths再点Add search path工具会自动把下级子目录展开成一串搜索路径然后确定。这个勾选项是省事的关键不勾只加一层勾了递归展开。递归展开的代价是路径条目可能几十条解析时会逐个尝试大工程上分析时间会明显变长。我的取舍是头文件目录结构规整就勾结构乱、子目录里混着第三方库的就手写关键几条宁可配得少也不要把无关目录塞进去。第二种方式是直接编辑dat。点界面上标识右侧的Edit在文件里按行写路径每行开头用1表示该语句生效用0表示注释掉。# include_paths.dat头文件搜索路径配置 # 每行首字符 1 生效0 注释新建的 dat 文件默认没有任何生效行 1 /project/mysdk/include 1 /project/mysdk/include/platform 1 /project/mysdk/include/driver 0 /project/mysdk/include/legacy 0 /project/mysdk/third_party/old_sdk这段配置的规则很直接每行一条绝对路径前面那个1是开关不是路径的一部分。工程临时不需要某个目录把1改成0就行别删行——删了下次想加回来还得重新敲路径留着注释是最便宜的后悔药。路径一定要写绝对路径相对路径会相对到工具进程的当前工作目录那个目录在哪取决于你从哪个快捷方式启动的不可控。注意dat文件里路径不要用反斜杠也不要加引号。带空格的头文件目录在这个文件里很难表达清楚遇到这种第三方库我一般会先做一次目录软链接把带空格的名字映射成不带空格的再写进dat。3.2 手写 dat 文件时的行序与优先级路径条目的顺序有讲究。搜索路径是顺序匹配的同名头文件出现在两个目录里时前面的目录胜出。所以顺序应该按“工程自有头文件 → 平台适配层 → 第三方库”排不要把第三方库放前面。这个坑我踩过第三方库里有个config.h工程自己也有一份第三方目录排在前面结果所有编译单元都吃到了第三方的config.h宏展开全错分析能跑完但分支数量跟实际代码对不上查了两天才定位到是路径顺序。另外新建的dat文件是空的没有任何生效语句这时候点确定工具不会提示“你没配任何路径”它会照常分析然后在日志里刷一堆找不到头文件的告警。所以每配完一轮我都习惯把分析日志翻到最前面看未定义符号的数量是不是清零了。没清零就回去改dat别带着一堆告警往下走。3.3 宏定义的添加与常见写法差异宏的配置入口和头文件在一块点Macros创建或打开一个dat文件然后点Edit Default进去编辑把需要开放的宏复制粘贴进去。什么叫“需要开放的宏”就是那些会改变代码结构的条件编译开关。典型的比如硬件平台选择宏、功能裁剪宏、调试开关。这些宏如果没在分析时定义对应的代码块会被预处理器整段去掉工具根本看不到覆盖率当然也不会统计——你以为是 100%其实是那部分代码压根没进分析范围。写法上我一般按下面的形式组织按用途分组组内按依赖顺序排# macros.dat分析阶段需要开放的宏定义 # 格式NAME 或 NAMEVALUE一行一个 # 平台选择必须最先打开后面的宏依赖它 PLATFORM_TARGETPLATFORM_A # 功能裁剪 ENABLE_LOG_MODULE ENABLE_CAN_DRIVER # 缓冲尺寸这类宏会影响数组边界务必和实际出包一致 RX_BUFFER_SIZE512 TX_BUFFER_SIZE256 # 调试相关默认关闭避免把调试专用分支算进覆盖率分母 # DEBUG_TRACE分组的价值在排查覆盖率异常时第一反应应该是“是不是有宏没开”而不是“用例设计得不够”。按组注释掉再跑一遍看函数数量和分支数量的变化能很快定位是哪一组宏影响了分析范围。这里有个版本差异要提醒不同大版本对#define前缀的容忍度不一样有的版本里写#define XXX和写XXX等价有的版本会把你写的#define当成宏名的一部分。稳妥做法是统一不写#define前缀只写宏名和值让工具自己去拼。改完dat之后回到工程重新做一次分析宏才会生效——只改文件不重新分析界面上的结果还是旧的。4. 单元测试九步配置从选择分析项到三份报告落盘环境配好、头文件和宏落地接下来是手册里的主体流程。这九步的顺序不能乱乱一步就得从头重跑尤其是覆盖率统计那一步中途改配置会污染结果。4.1 选择分析项前四项与 UnitTest only 的差别选文件走File/Select File选中待测的.c文件。然后进Select Analysis界面勾选前四项点Start Analysis等分析完成。前四项覆盖的是代码结构层面的解析结果具体项名以你装上的版本界面为准但顺序和分组是一致的别只勾一两项缺项会导致后面的测试序列里函数列表不全。分析完成后界面上有个按钮可以直接进单元测试界面如果前面没做分析直接点UnitTest only工具会默认把前四项分析先跑一遍再进单测界面效果一样。区别在于先跑分析的路径你能在进单测之前就看到告警和未定义符号UnitTest only是打包执行出了问题得回头单独排查。大工程上我倾向先跑分析、看日志、再进单测多一步但省时间。4.2 创建测试序列与全局变量赋值进单测界面后Sequence/New建序列序列名填待测函数名。这不是形式主义——回归报告里序列名是第一眼看到的东西用函数名命名出问题的时候能直接定位到是哪个函数别人接手也省事。安装后第一次做单测弹框里有几个选项要勾上这些选项和驱动生成、覆盖率统计相关第一次不勾后面覆盖率出不来还得回来重配。勾完点continue在界面右侧的函数列表里右键待测函数Create New Test Case一路continue到用例生成。生成之后看界面右下角工具会自动列出这个函数涉及的输入和输出变量。这里有个细节输出变量如果自动生成时Value显示为suspend直接右键remove掉。suspend在回归报告里会算进Suspends那一列的百分比留着它报告里永远有个非零的挂起率验收的时候说不清。输入变量分两类别搞混变量类型界面标识赋值位置说明函数入参Input parameter applied through local只能在用例处赋值或做映射对应函数形参不能当全局变量处理全局变量Input global右下角直接赋值或 Code Inserts 里赋值函数体内部引用到的外部变量Input global有两条赋值路径二者只能选一条。路径一是右下角直接填值路径二是右键用例 →Modify Existing Test Case/Code Inserts/Initialisation Code在弹框里写初始化代码。如果走路径二必须先把右下角自动生成的那条Input global删掉否则同一个变量被赋两次实际生效的是哪一次取决于执行顺序这种不确定性最难查。4.3 指针与数组入参的映射入参是指针或数组时不能直接填个数字了事得把它映射成一个同类型的全局实体工具才能通过这个实体去读写内存。这是整个流程里最容易卡住的一步。方法一在指针或数组的Value处右键选Point to global Entity或Assign to global弹框里New Name随便填类型会自动带出来。如果映射后提示变量未声明多半是工程里用了自定义类型比如typedef struct套了好几层工具不认这时候在Declaration Type里填原指针或数组的基本类型比如unsigned char或intArray Bounds填数组脚标大小Initial Value填初值。方法二菜单栏Global variables/Create User Global弹框和方法一一样但没有默认类型全部手填。建完之后回到指针的Value处填变量名如果映射的是数组填变量名[n]形式。两种方法的差别在复用方法一每条用例单独建实体隔离性好但用例多了之后全局变量表会很长方法二建的是用户全局多条用例共用同一个实体适合那种“整个模块共用一个接收缓冲区”的场景。我一般默认用方法一只有明确知道多条用例共享同一块内存时才用方法二。4.4 桩函数返回值与用例运行函数体里调用了外部函数那些函数在单测环境里没有实现就得打桩。界面上有标记位置双击能看到当前函数调用到的所有函数右键选中目标函数Set Return Value on Exit在Value里填返回值。桩的返回值要和用例设计对应上正常路径给正常值异常分支给错误码别所有桩都返回 0那样异常分支永远覆盖不到。运行用例有两条路。第一条第一条用例可以在空白处右键Run Test Case Driver或者点界面上方的运行按钮也可以按Generate Driver → Build Driver → Execute Driver → ProcessRegression Driver Output四步分开点。分步点的好处是每一步的错误都能单独暴露生成失败是配置问题编译失败是头文件或宏问题执行失败是桩或内存问题。我一般第一条用例一定分步走把链路跑顺。第二条后续用例可以右键单条Resequence This Test Case单独跑或者Resequence This and Subsequent Test Cases把选中的和它后面的逐条各跑一遍。单独跑的意义在于对比当前用例的预期输出和实际输出摆在同一个界面上哪条断言没过一目了然。4.5 三份报告的保存TCF、覆盖率、回归用例通过Pass之后右上角能看到当前函数的覆盖率概况。详细的要看Results/Dynamic Coverage Analysis Report语句、分支、MC/DC 三类都在里面。报告保存是三份别只存一份# 三份产物的保存位置建议按序列名归档方便回归时比对 /project/tb_work/report/函数名.tcf # Sequence/Export Test Cases (TCF) - save as /project/tb_work/report/函数名_coverage.rpt # Results/Dynamic Coverage Analysis Report - save as /project/tb_work/report/函数名_regression.rpt # Results/Regression Report - save astcf是测试用例文件导出它等于把这一轮设计的输入、预期输出、桩配置全部固化了。下次回归直接导入不用重新点一遍。覆盖率报告和回归报告是给人看的结果件命名里带上函数名归档时不至于混成一堆report1.rpt。覆盖率到 100% 之后先别急着交手册里给了一步很多人跳过的操作Source/Delete LDRA Testbed workfiles弹框里勾上对应选项后ok把历史覆盖率清掉然后按 4.4 的方法一重跑一遍再看覆盖率。原因是历史工作文件里残留着之前的执行记录覆盖率可能被“凑”到 100%清掉重跑才能确认是真覆盖还是假覆盖。这一步我吃过亏报告显示 100%清完重跑只有 87%差了三个 MC/DC 条件没覆盖。5. 排查记录Testbed 单测里最常见的五处翻车下面五条按“现象 → 原因 → 解决”写都是在这一套环境里反复出现的遇到时不用从零开始猜。第一条分析能跑完但函数列表里少了几个函数。现象是右侧列表里只有一部分函数缺失的那些在源文件里明明存在。原因通常是宏没开整段代码被预处理器裁掉了工具看不到。也可能是头文件路径里少了某个目录导致前置声明缺失、解析中断在函数之前。解决方法是先把宏dat里所有注释行打开跑一遍看函数数量是否变化没变化就翻分析日志里的未定义符号按符号名反查头文件该在哪个目录补进路径dat。第二条指针入参映射后提示变量未声明。现象是在Value处做完Point to global Entity跑用例时编译器报未声明标识符。原因是工程里用了工具不认的自定义类型映射出来的变量声明用了那个类型名。解决方法是在映射弹框的Declaration Type里改成基本类型Array Bounds填上数组大小Initial Value给初值。改完之后不需要重建用例重新生成驱动就生效。第三条用例全 PASS但覆盖率就是不到 100%。现象是回归报告Status全 PASS、Fails为 0覆盖率报告里分支或 MC/DC 有蓝色未覆盖项。原因有三类一是用例设计只覆盖了正常路径异常分支没给输入二是桩函数返回值恒为正常值短路求值的后半段根本没执行三是历史工作文件残留。解决顺序是先清工作文件重跑排除第三类再看覆盖率报告里标蓝的分支条件针对性地补一条用例或改桩返回值。第四条生成驱动就失败日志为空。现象是点Generate Driver之后界面没反应日志文件是空的或者只有一行时间戳。原因是工作目录路径里带空格或中文工具写中间文件时失败又不报错。解决方法是把工作目录换到纯英文无空格路径重新做一次分析再生成。这类“无日志失败”八成是路径问题别先去怀疑工程代码。第五条回归报告的Suspends列不是 0。现象是Status全 PASS但Suspends显示一个非零百分比。原因是输出全局变量自动生成时Value为suspend的那些变量没删。解决方法是在用例右侧的变量列表里逐个检查Value为suspend的输出变量右键remove只保留真正需要比较的输出变量重跑后该列归零。注意手册里“部分问题解决方法”那一节指向的是两篇外部排错笔记链接会过期但里面归纳的排查思路和上面五条是同一套先看日志、再看配置、最后才怀疑代码。顺序反过来会浪费大量时间。6. 装完怎么验证拿覆盖率报告和回归报告当验收单装完、配完、跑完用例最后一步是确认这套环境真的能出可信的结果。我一般不看“用例数量”这种指标只看两份报告里的三个字段。先看覆盖率报告。Results/Dynamic Coverage Analysis Report里函数名可以点击跳转到对应的代码位置语句、分支、MC/DC 三层依次往下排。颜色的含义要记牢报告区域颜色含义语句列中间列绿色当前或最后一次单独执行的用例覆盖到的语句语句列右侧列黑色所有执行过的用例累计覆盖到的语句分支区黑色已覆盖的分支分支区蓝色未覆盖的分支MC/DC 表格yes该逻辑组合已被覆盖MC/DC 表格no该逻辑组合尚未覆盖中间列的行为差异直接对应两种运行方式按 4.4 的方法一整体跑中间列显示全部执行过的用例的覆盖情况按方法二单独跑中间列只显示当前或最后一条用例的情况。手册里把这一节的章节号写成了 4.2.7实际指的是 3.2.7 那一节翻的时候别被编号带偏。MC/DC 那块要重点看。表格上半部分是判断条件的实际代码行号和代号C1、C2、C3中间显示它们的逻辑关系下面逐行列出需要覆盖的所有组合。如果某个组合是no说明这条用例没有构造出让该条件独立影响结果的数据——这不一定是代码问题多半是输入值选得太“好”两个条件同时为真或同时为假独立影响性测不出来。补用例的方向是把两个条件拆开一次只让一个变化。再看回归报告。开头是序列描述往下是逐条用例的执行结果重点三个字段Status必须是PASS。出现非 PASS 的用例先确认是不是桩返回值配错了再看输入是否越界。Fails必须是 0。这个字段是失败率非零就意味着有断言没过。Suspends必须是 0。非零说明还有suspend状态的输出变量没清理。这三个字段全绿再往下的输入输出明细才值得看。明细里会列出每条用例的输入值、预期输出、实际输出和桩函数详情做代码评审的时候直接把这段贴进评审记录比口头描述“我测过了”有用得多。我现在的习惯是每配好一个新的被测模块先拿最小文件跑通一遍清一次工作文件再重跑确认覆盖率不变最后把tcf、覆盖率报告、回归报告三份一起归档。这套动作看着啰嗦但它能挡住“假 100%”和“环境漂移”这两类最难复现的问题——从那以后我每次换机器重搭这套环境都强制走一遍清理加重跑的流程没再出现过报告和实际代码对不上的情况。希望帮到你。本文还有配套的精品资源点击获取