简介:《Testbed工具手册—单元测试方法V1.0》面向使用 C/C++ LDRA Testbed 开展白盒与单元测试的开发、测试工程师,侧重解决从环境配置到覆盖率报告落地的操作问题。压缩包仅含 1 个 doc 文件,大小约 2.01MB,以图文步骤形式组织,便于按章节查阅。内容覆盖头文件路径与宏配置、分析项选择、测试序列创建、全局变量赋值、输入输出变量建立、入参指针或数组映射、桩函数返回值设置、用例运行、覆盖率查看与报告保存,并给出错误信息、测试日志、软件版本等排错思路及覆盖率、回归报告查看方法。已有 6123 人学习,适合需要快速上手 Testbed 单元测试流程或查漏补缺的读者参考。
1. 一份手册能省掉多少试错
第一次用 Testbed 做 C/C++ 单元测试,卡住人的往往不是用例设计,而是分析跑不起来:头文件路径没配对、宏没打开、指针映射完提示变量未声明。这份 V8.2.0 的操作手册把白盒测试从「选文件」到「导出回归报告」拆成九个动作,每一步都落在具体菜单和弹框上,属于那种照着点就能走通的落地文档。它解决的核心问题是把「覆盖率怎么跑到 100%、跑出来凭什么可信」这件事流程化——语句、分支、MC/DC 三类覆盖在同一张动态报告里看,回归报告用 Status、Fails、Suspends 三列判定用例有效性。适合刚接手白盒测试任务的工程师、需要给团队定单元测试规程的测试负责人,以及被「覆盖率不达标」卡过的开发。新手能照着走,熟手更该看的是后面几章的判读和复核细节。
2. 静态配置与宏开关:两个 dat 文件为什么决定后面 80% 的坑
2.1 分析器看不懂代码,用例写得再漂亮也没用
单元测试工具和编译器在「读代码」这件事上的逻辑不一样。编译器遇到没见过的东西直接报错退出,Testbed 的分析器则是先做一遍静态解析,把函数、分支、判断条件抽出来建索引,遇到不认识的类型或缺失的头文件,它不一定当场停住,而是把某一段代码静默跳过。结果就是分析能跑完、界面也能进,但覆盖率报告里少了一大块分支,后面怎么设计用例都补不上。这类问题最难查,因为它不报错。
所以配置这一步的正确目标不是「让分析启动」,而是「让分析器看到的代码和编译器看到的一致」。判断标准很朴素:分析完成后,函数清单里的函数数量、每个函数的圈复杂度,应该和你用代码静态扫描工具算出来的基本吻合。对不上,就回头查头文件和宏。我一般会在选完文件之后先跑一次分析,把函数清单和分支数量记下来,配置改完再跑一次做对比,两次数字一样才继续往下走。这个习惯看着啰嗦,但能省掉后面几小时的玄学排查。
2.2 头文件路径:Search Options 点选和手改 dat 两种路子
入口在 File / Select File,先选中待测的 .c 文件。接着点 Configure / Static Options 配头文件,第一次配置时会提示创建一个 dat 文件,存放目录按自己的工程习惯放,建议放在被测模块同级的配置目录下,别丢在工具安装目录里,否则换台机器就找不到。创建好之后点弹框中间的 Search Options,再点头文件路径栏右侧的「…」,浏览到 include 目录。
如果头文件分散在多级子目录里,勾上 Add Subdirectories as search paths,再点 Add search path,最后确定。这一步等价于编译器命令行里的一串-I,只是把顺序和层级换成了图形化操作。需要注意的是,搜索路径是有先后顺序的,同名头文件出现在两个目录里时,排在前面的生效。工程里存在同名但内容不同的配置头文件时,顺序错了就会出现「明明改了这个文件、分析结果却没变」的情况,血泪经验。
第二种路子更可控:点同一界面的 Edit,直接编辑 dat 文件。格式很简单,每行开头一个数字表示这一行是否生效,1 有效、0 视为注释。
1,C:\proj\app\include 1,C:\proj\app\include\driver 0,C:\proj\legacy\include 1,C:\proj\third_party\hal\include逻辑说明:前三行是老工程的旧路径,第二行被 0 注释掉之后就不参与搜索,但保留在文件里方便以后恢复;1 开头的三条路径按从上到下的顺序进入搜索队列。参数说明:路径末尾不要带通配符,目录层级写到包含 .h 文件的那一层即可,如果写到了上一级又没勾 Add Subdirectories,就会静默找不到头文件。新建的 dat 文件里一行有效语句都没有,是正常现象,直接按相同格式往里加就行。
提示:dat 文件建议纳入版本管理,和被测代码一起提交。换人接手时,路径配置跟着代码走,比口头交接可靠得多。
2.3 宏定义:把 #ifdef 后面的分支放出来
嵌入式 C 代码里大量逻辑藏在条件编译后面,#ifdef DEBUG、#if (CFG_CHANNEL_NUM > 2)这类写法在编译时只保留一支。分析器如果拿不到宏的值,要么按未定义处理,要么整段跳过,两种情况都会让覆盖率虚高或虚低。配置入口在 Macros,操作方式和头文件类似:点 Find/Creat 创建或打开一个宏 dat 文件,再点 Edit Default 编辑,把需要开放的宏逐行粘进去。
格式和头文件 dat 一致,也是 1 生效、0 注释。带值的宏直接写宏名=值,不带值的写宏名即可。
1,UNIT_TEST 1,CFG_CHANNEL_NUM=4 1,CFG_BUFFER_SIZE=256 0,BOARD_REV_A逻辑说明:UNIT_TEST 用来打开测试专用分支,通常是代码里预留的桩入口;CFG_CHANNEL_NUM 这类数值宏必须给出具体值,只写宏名不带值的话,涉及数组维度声明的代码会在分析阶段报维度非法;BOARD_REV_A 被注释掉,表示当前测试目标不是这块板子的分支。参数说明:宏的值要和分析目标代码分支的要求一致——想覆盖>2的判断,就得给一个大于 2 的值,给 1 的话那个分支在源码层面就不存在,用例再怎么写也覆盖不到。
这里有个取舍:宏不能一次打开太多。把所有宏都定义为有效,可能会激活大段与当前测试目标无关的代码,函数清单暴涨,覆盖率分母变大,反而更难达标。常规做法是按模块建多份宏 dat,测哪个模块加载哪份。
2.4 分析项选择:前四项与 UnitTest only 的实际差别
配置完成后进入 Select Analysis 界面,勾选前四项,点 Start Analysis,等进度条走完。手册里提到的前四项通常覆盖了静态调用关系、数据流、复杂度这类基础信息,是创建测试序列的前提。分析完成后点标识按钮进入单元测试界面;如果没做这一步分析就直接进单元测试界面,界面上有个 UnitTest only 按钮,点它工具会默认先把前四项分析补齐再进用例环节。
两条路的结果一样,差别在可见性。先单独跑分析,能在进入用例设计之前就看到函数清单、调用关系、复杂度,配置错了当场能发现;直接点 UnitTest only 是把配置问题和用例问题混在一起排查,出错时不好定位是哪一环。我一般建议第一次接触某个模块时走前者,等这套配置稳定复用了,再图省事点后者。
分析阶段常见的失败原因就三类:头文件路径不全导致类型未定义、宏值不对导致数组维度非法、文件里存在分析器不支持的语法扩展。前两类靠上面的 dat 文件解决,第三类比较麻烦,通常要把那一小段代码用宏包起来排除掉,或者在工具的类型声明配置里手工补一个等价定义,具体看报错指向的行。
3. 测试序列构造:入参、全局变量、指针映射和桩函数四步拆解
3.1 建序列:命名规则决定了半年后的回归成本
在单元测试界面点 Sequence / New,弹出框里填序列名称。手册给的建议是「一般为待测函数名称」,这条建议值得严格执行,甚至可以更细一点:模块名_函数名_序号。原因是回归报告里序列名是唯一定位标识,一个模块几十个函数、一个函数十几个用例,名字起成 test1、test2,三个月后回归失败时没人知道对应哪段逻辑。
首次安装工具后建第一个序列时,弹框里有几项需要勾选,手册提示了这一点但没展开。这几项主要影响驱动生成的方式和用例代码的模板结构,勾错的表现是生成出来的驱动编译不过,或者用例里取不到入参。稳妥的做法是第一次按手册图示勾选,之后保持同一套设置,别在不同序列之间来回改。
点 Continue 之后在界面右侧选中待测函数,右键 Create New Test Case,再一路 Continue 直到用例生成。工具会自动生成一份默认的输入输出变量列表,摆在界面右下角,这份自动生成的内容只反映函数签名的形参,函数体里用到的全局变量不一定齐,后面要手工补。
3.2 两类输入:Input parameter applied through local 与 Input global
右下角的变量列表里,输入项分两种 Use 类型,这是整个用例构造环节最容易混淆的地方。Input parameter applied through local 对应函数形参,它的值只能在这里赋,或者映射到一个全局实体上,没有第三种入口。Input global 对应函数体里直接引用的外部全局变量,赋值有两个入口:一是直接在右下角列表里填 Value,二是在左下角用例上右键 Modify Existing Test Case / Code Inserts / Initialisation Code,在弹框里写初始化代码。
这两个入口不能同时用。如果右下角已经自动生成了一条 Input global 赋值,而你需要用 Code Inserts 里那种更灵活的写法(比如赋一个表达式、调一个初始化函数),必须先把右下角那条删掉,否则两处赋值会打架,实际生效的往往不是你预期的那条,表现是用例跑通了但结果对不上。这条规则手册写得很短,实际踩的人不少。
选择原则可以归纳成一句话:能用列表里直接填值解决的,就别写代码。Code Inserts 适合的场景是赋值为地址、结构体多字段初始化、需要先调用一个准备函数这类列表填不了的情况。用得越少,用例越容易被别人看懂,回归时也越好维护。
3.3 手工建 IO 变量:Assign 与 Compare Write 的选择
自动生成的列表不覆盖所有情况,比如函数通过指针形参回写数据、或者需要校验某个全局变量在函数执行后的值。这时在右下角空白处右键 Create IO Variable In Test Case,弹出框里四个字段都要填对。
| 字段 | 填写要点 | 常见错误 |
|---|---|---|
| Name | 变量名,需与被测代码里的标识一致 | 拼写不一致导致映射不上 |
| Type | 数据类型,结构体需写完整类型名 | 自定义类型写成基础类型 |
| Use | Input Global 或 Output Global | 输入输出方向选反 |
| Regression Analysis | 输入选 Assign,输出选 Compare Write | 输出选了 Assign,等于不做校验 |
| Value | 输入值或预期输出值 | 输出值填成了实际运行结果 |
重点在 Regression Analysis 这一列。Input Global 必须选 Assign,表示「执行前把这个值写进去」;Output Global 必须选 Compare Write,表示「执行后拿实际值和这里填的预期值比对」。如果把输出变量也设成 Assign,用例运行不会报错,回归报告里 Status 依然是 PASS,但这个变量实际上完全没被校验,等于白写。这种「假通过」比失败更危险,因为它不会触发任何告警。
Value 里填的必须是预期值,不能是跑完之后从结果里抄回来的实际值。抄回来的做法在覆盖率上看着很漂亮,但回归失去了意义——代码改错了,预期值跟着错,永远 PASS。这个道理说起来简单,赶进度的时候最容易破功。
3.4 指针与数组映射:两种建法及「变量未声明」的根因
函数入参是指针或数组时,不能直接填值,得先映射成一个同类型的全局实体。方法一是在该变量的 Value 处右键,选 Point to global Entity 或 Assign to global,弹框里点 New,Name 栏随便填一个有意义的标识,类型会自动生成。方法二走菜单 Global variables / Create User Global,弹出的框和方法一相同,但没有默认类型,全部要手填。
两种方法的差别只是入口,填完之后都要回到指针变量的 Value 处写映射关系:映射成标量写&变量名,映射成数组写变量名[0]或变量名[n]。
/* 被测函数原型(虚构示例) */ int parser_frame(const unsigned char *in_buf, unsigned int len, unsigned short *crc_out); /* 用例里的映射关系,写在对应变量的 Value 处 */ /* in_buf -> &g_in_buf_storage (映射为全局数组) */ /* crc_out -> &g_crc_out_storage (映射为全局标量) */逻辑说明:映射的本质是给形参一个稳定的内存落点,让工具在生成驱动时知道这块数据从哪来、往哪去。参数说明:映射后的全局实体类型必须和形参指向的类型一致,unsigned char *映射到int数组上,驱动能编译过但取到的值是按字节还是按字对齐就说不准了。
手册里点出了一个高频故障:映射完成后运行,报「变量未声明」。原因不是映射没建成功,而是 Testbed 不识别研发自己定义的类型——比如形参是typedef struct cfg_item *这类别名。解决办法是在弹框的 Declaration Type 处,把原指针或数组类型改填成它的基本类型(比如void *或具体的基础类型),Array Bounds 处填数组脚标大小,Initial Value 处填初值。改完之后工具就能自己生成合法声明,报错消失。
这个坑的特征很好认:报错指向的是映射出来的那个全局实体,而不是原形参。看到这个指向,直接去查类型的 typedef 链条,一层层剥到基础类型为止。
3.5 桩函数返回值:Set Return Value on Exit 的边界
被测函数调用了外部函数时,单元测试要把这些调用切断,否则就变成了集成测试,结果不可控。手册的做法是在界面标记处双击,展开函数中调用到的函数清单,右键选中目标函数,点 Set Return Value on Exit,在 Value 处设返回值。
这一步的关键是「设哪些、不设哪些」。判断依据是分支覆盖需求:某个被调函数的返回值决定了被测函数走哪条分支,这个返回值就必须设,而且要针对不同用例设不同值,才能把分支都覆盖到。反之,返回值不参与任何判断的调用,设不设无所谓,设了反而多一条维护负担。
对于有输出参数的被调函数,光设返回值不够,还得让它在退出时把输出参数填上。这类场景常见做法是在 Code Inserts 里补一段赋值,或者在映射时把输出参数指向一个预置好数据的全局实体。手册没展开这一层,但实际项目里带出参的接口很常见,不处理的话,被测函数拿到的是未初始化内存,行为随机,用例时通时不通,很难查。
桩函数设置还有一个容易忽略的边界:同一个被调函数在不同用例里需要返回不同的值,这些设置是跟着用例走的,不是跟着序列走的。改完一个用例的桩值,别忘了另一个用例里可能还留着旧值,批量回归时会以为什么都没动、结果却变了。
4. 运行与覆盖率判读:语句、分支、MC/DC 三张图怎么读才算数
4.1 四种运行路径的差别与适用场景
手册给了两条运行路子,细分下来是四种操作。第一种是在界面空白处右键 Run Test Case Driver,或者点上方工具栏里对应的按钮,一步到底。第二种是依次点 Generate Driver、Build Driver、Execute Driver、Process Regression Driver Output,分四步走。两者结果一致,区别在出错时的定位精度:分步走能明确告诉你问题出在驱动生成、编译、执行还是结果解析,一步走只会给一个笼统的失败提示。
我一般建议新模块的第一个用例用分步,把链路走通一次,确认驱动生成规则、编译器配置、执行环境都没问题;后续用例再用一步到底提效率。这个习惯看着慢,实际上第一次就把环境问题暴露干净了,后面几十个用例都不会再被环境问题打断。
第三种是右键某个用例选 Resequence This Test Case,单独跑这一个;第四种是 Resequence This and Subsequent Test Cases,从选中的用例开始往后逐个单独跑。这两种方式的意义在于方便逐条比对预期输出和实际输出——手册点明了这一点,但没说的是它们对覆盖率显示的影响。
4.2 覆盖率报告的三列颜色与跳转
用例通过后,界面右上角直接显示当前函数的覆盖率概况。要看细节就点 Results / Dynamic Coverage Analysis Report。报告里每个函数前面有覆盖率数值,点函数名可以跳转到该函数在本页的具体位置,往下翻就是语句和分支的逐行标注。
语句覆盖部分有三列信息,读法取决于用例是怎么跑的。如果用的是 4.1 里的一步到底或分步方式(批量执行),中间一列显示的是所有执行过的用例累计的语句与分支覆盖情况;如果用的是 Resequence 单独执行,中间一列只反映当前或最后一个单独执行的用例。右侧一列始终是「当前所有执行过的用例」的累计情况。
颜色含义需要记牢:覆盖到的语句显示为绿色或黑色,初次覆盖或单独运行时中间一列显示绿色;未覆盖的语句在分支区域会显示为蓝色。这个配色不是装饰,判读时看蓝色就行——蓝色的分支就是还缺用例的地方,直接对着它设计下一个用例的输入值。
有个细节容易误判:单独执行某条用例之后,中间列会变成只显示这一条用例的覆盖,看起来覆盖率突然掉下来,其实右侧累计列没变。如果不注意这一点,会误以为之前的用例白跑了,反复重跑,浪费时间。
4.3 MC/DC 表里的 C1、C2、C3 与 yes/no
分支覆盖下面还有一块 MC/DC,这是白盒测试里要求最高的一类。报告会显示实际代码行号和判断条件的原始写法,然后把复合条件里的每个原子条件用 C1、C2、C3 这样的代号标出来,在下方的表格里引用。
表格分三块:第一块列出 C1、C2、C3 之间的逻辑关系(与、或、非的组合);第二块逐行列出需要覆盖到的所有条件组合;第三块在中间列和右侧列标注每条组合是否已覆盖,显示 yes 表示覆盖到,no 表示没覆盖到。
判读方法很直接:从上往下找 no,找到一行就是一个缺口。缺口对应的那组条件组合,就是下一条用例要构造的输入。相比语句覆盖,MC/DC 的缺口往往不是「某行没执行」,而是「某行执行了但某个原子条件没独立影响结果」,这类缺口靠加用例数量补不上,必须精准构造。
举个典型场景:条件是if (a > 0 && b == 1),如果所有用例里 a 和 b 都是同真同假,那分支覆盖可能是 100%,但 MC/DC 一定不满,因为缺少「a 为真、b 不为 1」或「a 为假、b 为 1」这类让单个条件独立起作用的组合。这种时候光看分支覆盖率会误判成已经做完,必须切到 MC/DC 表确认。
4.4 回归报告:Status、Fails、Suspends 三列先看哪个
回归报告的开头是测试人员写的序列描述,往下是用例执行结果表。手册点明了三列的含义,实际判读有先后顺序。
Status 是用例执行结果,正常情况不会出现无法执行的用例,所以看到非 PASS 就是硬故障,先解决它,别的列都不用看。Fails 是失败率,如果单元测试过程没遇到问题,这一列必须全为 0——不为 0 说明预期输出和实际输出对不上,要么代码有缺陷,要么预期值填错了,两种都要追溯到具体用例并提问题单。Suspends 显示的是输出全局变量里 Value 为 Suspend 的百分比,这个值本质上是「有多少输出压根没被校验」,数值偏高意味着用例的校验强度不够,覆盖率再高也说明不了问题。
三列之外还有函数名、文件名、输入输出个数几列,用来核对用例覆盖面:某个函数的用例输入个数明显少于形参个数,大概率有参数没赋值。报告下方逐条列出每个用例的描述、输入输出值和桩函数设置,回归失败时就是在这里定位是哪条用例、哪个变量出的问题。
4.5 报告导出与归档
三份东西要保存,入口分散在三个菜单下:Sequence / Export Test Cases(TCF),保存的是测试用例本身,后续回归可以直接加载复用;Results / Dynamic Coverage Analysis Report,保存覆盖率报告;Results / Regression Report,保存回归结果。三份都点 Save as,存成独立文件。
归档建议按「模块 / 版本 / 日期」三级目录组织,TCF 文件和代码一起提交。原因是单元测试的可重复性完全依赖这份 TCF——换了台机器、重装了工具,只要 TCF 和 dat 配置在,用例就能原样跑起来;只存 PDF 报告的话,下次回归等于从零重写。
5. 常见问题排查:五条从分析报错到覆盖率虚高的现场记录
5.1 映射完指针就报「变量未声明」
现象:指针或数组形参按 Point to global Entity 建好映射,运行用例时报变量未声明,报错位置指向映射出来的全局实体,不是原形参。
原因:形参类型是项目自定义的 typedef,工具的类型解析走不到最底层,生成驱动时不知道该声明成什么。
解决:回到映射弹框,Declaration Type 处填原类型剥到底之后的那个基本类型,Array Bounds 填数组脚标大小,Initial Value 填初值。改完重新生成驱动。判断是否属于这一类,看报错里的类型名是不是以_t或项目前缀结尾。
5.2 输出变量的 Value 一直是 Suspend
现象:用例自动生成的输出列表里,某些变量的 Value 显示为 Suspend,回归报告里 Suspends 一列数值偏高。
原因:这些变量在函数执行后的值无法自动推断,工具挂了占位符,实际没参与比对。
解决:先判断这个变量该不该校验。不参与逻辑判断的中间变量,右键 Remove 移除,减少噪声;该校验的,手工建一条 Output Global 变量,Regression Analysis 选 Compare Write,Value 填预期值。如果这个输出是结构体指针回写的,参考 3.4 的映射方式处理。
5.3 覆盖率显示 100%,清掉重跑就掉下来
现象:一轮用例跑完覆盖率全绿,按流程清 workfiles 重跑,覆盖率不满。
原因:覆盖率是累计统计的,前面调试过程中零散跑过的用例、甚至误跑的驱动,都会被计入历史覆盖数据。看似 100%,实际是多次不完整执行拼出来的。
解决:手册给的动作是 Source / Delete Testbed workfiles,在弹框里选中对应选项后确定,清掉历史覆盖记录,再用 4.1 里的一步到底方式完整重跑一遍,观察真实覆盖率。这个动作应该成为交付前的固定步骤,不能只看界面上的绿条。
5.4 单条用例跑通,批量回归全挂
现象:用 Resequence This Test Case 逐条跑都 PASS,一用批量执行就大面积失败。
原因:批量执行时用例之间共享全局状态,前一条用例改过的全局变量没有复位,后一条用例拿到的是脏数据。逐条执行时每次都是干净环境,所以看不出问题。
解决:给每条用例补 Initialisation Code,把用到的全局变量显式初始化,不依赖默认值。改完之后再用 Resequence This and Subsequent Test Cases 从第一条跑到最后一条验证一遍。这条坑的隐蔽性很高,因为两种运行方式的结果不一致本身就是线索。
5.5 Status 全 PASS 但 Fails 不为 0
现象:回归报告里 Status 列全是 PASS,Fails 列却有非零值。
原因:Status 反映的是用例能否执行完,Fails 反映的是预期值和实际值的比对结果,两者统计口径不同。输出变量设成了 Assign 而不是 Compare Write,或者预期值是跑完之后抄回来的,都会造成这种割裂。
解决:逐条核对输出变量的 Regression Analysis 设置,Assign 只允许出现在输入侧;预期值必须是事先按需求算出来的,不能引用运行结果。核对完重跑,Fails 归零才算这轮单元测试有效。
6. 报告复核:三个动作让覆盖率数字站得住
前五个环节走完,覆盖率数字出来了,但数字本身不等于结论。交付前我固定做三个动作,目的都是把「看起来达标」变成「复核过达标」。
第一个动作是清 workfiles 重跑。入口在 Source / Delete Testbed workfiles,弹框里选中覆盖数据相关的选项,确定之后历史记录清零。然后按 4.1 里的一步到底方式重新执行全部用例,观察覆盖率变化。之所以必须做,是因为调试期间的零散执行会污染累计数据,5.3 那条踩坑记录就是没做这一步的后果。重跑之后如果覆盖率比之前低,说明之前那个数字是拼出来的,得补用例。
第二个动作是对齐函数清单。把分析阶段导出的函数列表和需求文档、设计文档里的函数清单逐项对照,确认没有漏测的函数。工具只统计它分析到的函数,某个文件因为路径或宏的原因没被分析进来,覆盖率照样能显示 100%,但那个文件里的代码一行都没测。对照完之后再核一遍每个函数的用例数,用例数明显偏少的函数优先补。
第三个动作是查 MC/DC 表的 no。语句和分支覆盖容易刷,MC/DC 刷不动。打开动态覆盖率报告,逐个函数翻到 MC/DC 区域,从上往下数 no 的条数,把每个 no 对应的条件组合记下来,转成下一条用例的输入。这一步做完通常会多出三到五条用例,覆盖率数字可能只涨一两个百分点,但那几个点是真正补上的逻辑缺口。
| 复核动作 | 入口 | 判定标准 |
|---|---|---|
| 清 workfiles 重跑 | Source / Delete Testbed workfiles | 重跑后覆盖率不低于清理前 |
| 函数清单对齐 | 分析结果函数列表 | 与设计文档函数清单一一对应 |
| MC/DC 缺口清零 | Results / Dynamic Coverage Analysis Report | 逐函数无 no 行 |
还有个习惯值得提:TCF、覆盖率报告、回归报告三份文件按模块版本归档,TCF 跟着代码走。曾经为了赶一个节点,报告只存了 PDF 没存 TCF,两个月后要回归验证一个改动,只能把几十条用例重新敲一遍,那种后悔药没地方买。从那以后我每次跑完一轮,都强制走一遍「导出 TCF → 存覆盖率报告 → 存回归报告 → 清 workfiles 重跑验证」这四步,缺一步就不算完成。希望帮到你。
本文还有配套的精品资源,点击获取