ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基本路径覆盖法:圈复杂度与独立路径测试用例设计

基本路径覆盖法:圈复杂度与独立路径测试用例设计 “订单金额算错了多退了两千多块。”这是我前两年在电商项目里第一次真正被代码分支坑到。事后复盘那段结算逻辑一共五个判定点功能测试同事手工点了三十多条用例照样漏掉了“VIP 优惠券 大额订单 账户有欠款”这个组合。问题不在人不够细心而在于靠直觉堆用例永远不知道自己到底漏了多少。后来我把那段代码重新拿出来用基本路径覆盖法重算了一遍圈复杂度是 6也就是说只要 6 条测试用例就能把独立的逻辑通路全部走一遍。基本路径覆盖法Basic Path Coverage是软件测试里白盒测试的经典方法属于逻辑覆盖和路径覆盖的折中方案——它不去穷举所有可能的路径那是指数级的根本做不完而是用图论的办法算出一个最小的线性无关路径集合。这套方法特别适合分支密集、状态跳跃的代码银行软件测试里的计息和风控规则、车载以太网协议的状态机流转、嵌入式软件测试里的中断处理逻辑都能用。这篇内容我会从控制流图怎么画、圈复杂度怎么算、独立路径怎么推一路讲到用例怎么落地、覆盖率报告怎么看、AI 生成的用例怎么用它兜底全程按一个测试用例设计老手的实操节奏来。看完你能拿到一套可以照着抄的流程也能搞明白每一步背后的道理。1. 基本路径覆盖法到底在解决什么问题1.1 从一个漏测事故倒推方法的价值先说清楚这件事的边界。功能测试的测试用例设计方法里等价类、边界值、场景法擅长处理输入域的划分它们回答的是“我该拿哪些数据去测”。而基本路径覆盖法回答的是另一个问题“代码里到底有多少条互不相同的执行通路我有没有把每一条都走到”。这两个问题不是一回事很多人混在一起结果就是用等价类替代了路径分析看着覆盖挺全实际上分支组合是空的。软件测试圈子里有句话流传很广语句覆盖能到 100% 的代码缺陷照样能逃出来。原因很朴素——语句覆盖只关心“这行有没有被执行”不关心“从哪个方向进来的”。一段if (vip) discount 10;只要有一组 vip 为真的数据执行过语句覆盖就绿了可 vip 为假时 discount 该不该保持原值没人验证过。基本路径覆盖法就是来堵这个口子的。它的核心思路可以这样理解把被测函数画成一张有向图每个判定点就是一个岔路口然后找出这张图里所有“线性无关”的通路。线性无关这个约束很关键它保证了这些通路互相之间不能由其他通路线性组合出来因此覆盖它们的用例集合是最精简的不重复、不遗漏。基本路径测试的整套流程就是画控制流图 → 算圈复杂度 → 导出独立路径 → 把路径翻译成输入数据 → 形成用例表。1.2 它的适用场景与能力边界这套方法不是万金油。我的经验是它最适合三类代码第一类是判定节点在 3 到 15 个之间的业务逻辑函数用例数量可控第二类是状态机实现比如协议解析、订单状态流转、审批流引擎这类代码的“路径”天然就是状态迁移序列第三类是核心算法或计费逻辑一旦算错就是钱的问题值得花时间做结构化覆盖。反过来说三类场景不要用它。判定节点超过 20 个的巨型函数圈复杂度算出来吓人与其硬啃不如先做函数拆分重构本身就是降复杂度纯 getter/setter、纯配置映射这种完全直线型的代码语句覆盖就够了还有那种含大量浮点运算和数值迭代的代码路径分析帮不上忙得靠数值测试和数据驱动。另外要一直记住基本路径覆盖法只保证逻辑通路被走到它不负责验证数据对不对。金额计算对了是业务规则的事路径覆盖只保证每条业务规则都被触发过。注意基本路径覆盖法不能替代边界值分析。路径告诉你要走哪条路边界值告诉你走在路上时该踩哪些点。两个方法必须叠着用。1.3 和其他覆盖准则放在一起比一比不搞清楚各准则的差异写出来的用例集很容易重复和浪费。下面这张表是我自己在做测试方案评审时常用的对照清单看一遍心里就有数了。覆盖准则覆盖对象用例数量级主要能揪出什么问题落地成本语句覆盖每一条可执行语句最少死代码、未执行分支低判定覆盖每个判定取真/取假各一次中等分支被整体遗漏中条件覆盖每个原子条件的真假都出现中等偏多短路导致的单条件未验证中判定/条件覆盖上面两者同时满足较多大部分条件级缺陷较高条件组合覆盖判定的所有条件组合指数级条件之间的组合缺陷高基本路径覆盖线性无关的独立路径等于圈复杂度分支组合缺失、循环进出异常中高完全路径覆盖所有可行路径指数级甚至不可行理论上最彻底不可接受看表格最右边一列能看出门道基本路径覆盖法的用例数量等于圈复杂度是线性的这是它最大的实用价值。条件组合覆盖和完全路径覆盖是组合爆炸理论上很美工程上做不完。所以我的常规打法是先用等价类和边界值圈定数据范围再用基本路径覆盖法保证逻辑通路最后补少量条件组合用例处理高风险判定。2. 动手前必须啃透的四个硬概念2.1 控制流图把代码画成一张可走的地图控制流图Control Flow GraphCFG是整套方法的载体画错了后面全废。定义很直白节点代表语句或语句块有向边代表控制转移。实际操作时有几条简化规则从业者基本都是这么干的。第一连续的、不包含任何判定的语句序列可以合并成一个节点。比如int discount 0;后面跟int payable 0;中间没有分支就画成一个节点。这样图会干净很多。第二每个判定节点必须写清楚条件表达式因为后面要把路径翻译成数据时靠的就是这些条件。第三函数出口统一收敛到一个终止节点不管中间有多少个 return。第四return语句单独作为节点因为它会直接跳到出口。绘制工具上我不推荐一开始就用工具自动生成。AutoCraph、Codeviz 这类工具确实能直接从源码生成 CFG但生成出来的图节点极细几十个节点糊在一起看都看不清对理解帮助不大。我的做法是先用纸笔手工画一遍画的过程中对代码的理解会立刻加深一层很多平时没注意的短路逻辑会在画图时暴露出来。手工画完再对照工具生成的图检查有没有漏边两遍验证基本不会错。这里有个容易被忽略的细节逻辑运算符和||在大多数语言里都有短路特性a || b在 a 为真时根本不会求值 b。严格按条件覆盖的算法短路会让判定节点内部产生额外的控制流。基本路径法通常把整个判定当成一个节点处理但这个简化会掩盖短路缺陷这件事我在第 4 节会专门讲怎么处理。2.2 圈复杂度算出你到底要写几条用例圈复杂度Cyclomatic ComplexityMcCabe 复杂度是整套方法的“计数器”它直接告诉你独立路径有几条也就等于用例数量的下界。三种算法必须能互相验证我每次算都至少用两种方法确认一遍。第一种V(G) E − N 2其中 E 是边数N 是节点数。这是欧拉公式在平面图上的应用只要图是连通的就是这个式子。第二种V(G) P 1P 是判定节点的个数注意不是判定条件个数。这个式子最省事数判定就行但前提是你没把复合条件展开。第三种V(G) 封闭区域数 1也就是平面被边分割出的区域总数把图外面那个无限区域也算作一个区域。三个式子算出来必须相等不相等就说明图画错了八成是漏了边或者多画了节点。举个最简单的例子一个单独的if语句判定节点 1 个P 1 2画成图是 4 个节点入口、判定、真分支、出口配 5 条边E − N 2 3不对——是 4 个节点 5 条边得 5 − 4 2 3和 P 1 2 矛盾。问题出在我这里节点数报错了正确的图是入口、判定、真分支、假分支合流、出口。所以手工数节点时一定要把合流点算清楚这是新手最常翻车的地方。提示圈复杂度在 10 以内属于结构良好超过 15 就该考虑重构了。测试视角下一个圈复杂度 30 的函数意味着你至少要写 30 条路径用例光维护这些用例就是负担。2.3 独立路径与线性无关为什么 6 条就够“独立路径”这个词是本方法的核心也是最容易被含糊过去的地方。严格定义是一条路径如果至少引入了一条此前所有路径都没用过的边它就是独立的。用线性代数的语言说把每条路径表示成边的出现次数向量这些向量之间不能互相线性表出。为什么这个约束这么重要因为如果不加约束你可以随便造出无数条路径比如把某条环路多绕几圈那就是新路径但它的逻辑信息量是零。线性无关保证了每一条新路径都贡献一个新的判定方向因此 V(G) 条路径就能把图里所有判定的所有分支方向都覆盖到。实践中导出独立路径最省力的方法是基线路径法 单点翻转。步骤是这样的先选一条“主干”路径作为基线通常是包含判定节点最多、最贴近正常业务流程的那条然后从基线出发依次翻转每一个判定节点的结果每翻转一次得到一条新路径。这样得到的路径集合天然满足“每条都引入至少一条新边”也天然是 V(G) 条。这里要提醒一句基线路径怎么选会影响你能不能顺利翻转。如果基线选了一条很偏的路径翻转过程中很容易撞上不可行路径。我的经验是优先选“所有判定可能取主流值”的那条比如业务上最常走的那条审批通过路径。2.4 循环结构怎么画才不出错循环是基本路径法里最需要小心的地方。一个while循环在控制流图上其实是两个节点加三条边一个循环条件判定节点、一个循环体节点加上“条件为真进入循环体”“循环体回到条件”“条件为假跳出”三条边。折算到圈复杂度上一个单重循环贡献一个判定节点。这里有个约定俗成的处理方式基本路径法对循环的默认假设是“循环体最多执行一次”。也就是说从路径的角度我们只区分“一次都不进循环”和“进一次循环然后退出”这两种情况不区分进两次、三次。理由很简单进两次和进一次的路径在判定方向上是相同的线性无关集合不需要重复。当然这个假设有风险循环体的边界条件比如最后一次迭代、循环变量的初始值如果写错按这个假设设计的用例是抓不到的所以必须靠边界值分析补上。嵌套循环更麻烦。内外两层循环如果各按进出两种组合理论上就有四种但基本路径法依然只考虑进/不进靠圈复杂度公式自动决定总用例数。这里我的实操心得是循环相关的缺陷八成不在路径上而在边界和累加器初始化上。所以基本路径法处理完循环结构后一定要单独加一组边界用例比如数组为空、循环变量等于上限、累加器初始值被复用这个坑非常隐蔽累加器忘了清零第二次调用结果就错了。3. 完整走一遍从代码到6条可执行用例3.1 待测函数的选择与预处理理论讲完了直接上实战。我挑了段订单审核逻辑结构不复杂但判定数量合适能完整展示整个流程。原始代码如下。public static String auditOrder(int amount, int stock, boolean vip, boolean couponUsed, int balance) { if (amount 0 || stock 0) { // 判定1 return 参数异常; } int discount 0; if (vip) { // 判定2 discount discount 10; } if (couponUsed) { // 判定3 discount discount 15; } if (amount 200) { // 判定4 discount discount 20; } int payable amount - amount * discount / 100 - balance; return payable 0 ? 通过 : 拒绝; // 判定5 }选这段代码有几个考虑。第一它有五个判定节点圈复杂度是 6用例数量适中演示起来不啰嗦。第二判定 1 是复合条件||能顺便讲清楚复合条件的处理。第三折扣是累加计算的中间变量 discount 的值可以直接用来验证路径有没有走对这一点很重要——基本路径测试不只验证输出还应该验证关键中间变量的值否则不同路径可能碰巧产生相同输出用例就失去了区分度。第四最后一个判定用了三元运算符路径上等价于一个 if-else这点要说明不然有人画图时不知道 ternary 该怎么处理。预处理阶段还有件事要做确认代码里没有异常抛出、没有外部依赖调用。如果有throw或者远程调用控制流图会复杂不少基本路径法的收益会下降那时候更适合用场景法配合接口 Mock。3.2 画出控制流图并完成节点编号按 2.1 的规则把顺序语句合并得到 13 个节点。我一般会把节点列表和边列表都写下来因为这比画图更容易检查。节点内容出边N1判定1amount 0 || stock 0N2、N3N2return 参数异常N13N3int discount 0;N4N4判定2vipN5、N6N5discount 10N6N6判定3couponUsedN7、N8N7discount 15N8N8判定4amount 200N9、N10N9discount 20N10N10计算 payable 并判定5payable 0N11、N12N11return 通过N13N12return 拒绝N13N13出口—数一下边N1 出 2 条N2 出 1 条N3 出 1 条N4 出 2 条N5 出 1 条N6 出 2 条N7 出 1 条N8 出 2 条N9 出 1 条N10 出 2 条N11 出 1 条N12 出 1 条合计 17 条边。节点 13 个。这里有个实操细节值得说N10 把“计算 payable”和“判定 payable 0”合并成一个节点了。严格来说计算语句是非判定语句可以和判定合并成一个节点因为合并顺序语句是允许的而判定节点本身必须保留条件表达式。合并的好处是减少节点数让图更好看。但如果你要在路径里验证 payable 的中间值那这个值依然可以从输入算出来不影响。3.3 三种方法互验圈复杂度现在用三种算法交叉验证。方法一边减节点加二V(G) 17 − 13 2 6。方法二判定节点数加一判定节点是 N1、N4、N6、N8、N10共 5 个V(G) 5 1 6。方法三区域数这个得看着图数我这边的图封闭区域是 5 个加上图外的无限区域一共 6 个。三个结果都是 6说明图没画错。这时候心里就有底了独立路径一共 6 条用例数量下界是 6。顺便提一句判定 1 那个复合条件。如果坚持把amount 0和stock 0拆成两个独立判定那判定节点就变成 6 个V(G) 变成 7。这是第 4 节要展开的争议点先在这里埋个伏笔。我目前的处理方式是默认不拆除非这个复合条件涉及高风险业务规则。理由是不拆能保证用例数量线性可控拆了会带来用例膨胀而复合条件内部的缺陷通常有更专门的方法比如条件组合覆盖配合 MC/DC来处理。3.4 用基线翻转法推导独立路径集按 2.3 的基线翻转法操作。我选的基线路径是业务上最常走的“VIP 用券 大额订单 账户无欠款”也就是全部判定都取主流值的那条P1基线N1 → N3 → N4 → N5 → N6 → N7 → N8 → N9 → N10 → N11 → N13这条路径走过了 N5、N7、N9 三个折扣累加节点。接下来依次翻转每个判定翻转判定 1得到P2N1 → N2 → N13翻转判定 2vip 取假得到P3N1 → N3 → N4 → N6 → N7 → N8 → N9 → N10 → N11 → N13翻转判定 3couponUsed 取假得到P4N1 → N3 → N4 → N5 → N6 → N8 → N9 → N10 → N11 → N13翻转判定 4amount 取小值得到P5N1 → N3 → N4 → N5 → N6 → N7 → N8 → N10 → N11 → N13翻转判定 5payable 取非正得到P6N1 → N3 → N4 → N5 → N6 → N7 → N8 → N9 → N10 → N12 → N13现在验证一下独立性。P3 引入了边 N4→N6P4 引入了 N6→N8P5 引入了 N8→N10P6 引入了 N10→N12 和 N12→N13P2 引入了 N1→N2 和 N2→N13。每条路径都至少引入了一条此前没用过的边6 条路径线性无关正好等于圈复杂度。到这一步路径集合就确定了。不过我得诚实说一件事这个例子的翻转过程比较顺利是因为我事先设计过判定结构。真实代码里翻转经常撞上不可行路径。比如你翻转判定 5 想让 payable 非正结果发现按前面的判定组合payable 恒为正这条路根本走不到。遇到这种情况的处理办法见第 4 节。3.5 把路径反推成输入数据有了路径接下来是体力活为每条路径找一组输入数据让它真的能按这条路线走。这一步的关键是逐判定倒推约束。我按路径顺序列出每个判定需要的结果然后求解。以 P1 为例判定 1 要为假所以amount 0且stock 0判定 2 为真vip true判定 3 为真couponUsed true判定 4 为真amount 200判定 5 为真payable 0。取 amount 300、stock 10、vip true、couponUsed true、balance 0。算一下 discount10 15 20 45payable 300 − 300×45/100 − 0 165大于 0判定 5 为真。约束全部满足。P6 的约束比较难缠判定 1 到判定 4 全部维持基线的取值但判定 5 要取假payable 0。如果沿用 amount 300、vip true、couponUsed true那么 discount 45payable 300 − 135 − balance要让 payable 非正balance 得大于等于 165。取 balance 200payable −35判定 5 为假。这里就能看出 balance 这个参数在测试上的作用——它是唯一能让最后判定反转的自由变量。参数取值上有个经验优先选边界上的“刚好满足”值而不是随便取一个远离边界的值。比如判定 4 条件是amount 200P1 里我取 300 没问题但为了顺便覆盖边界我可以在 P5 里取 200刚好不满足在 P1 里取 201刚好满足。这样一组用例下来路径和边界一次覆盖完性价比最高。3.6 用例表与预期结果落库把上面所有推导整理成正式用例表。这张表是可以直接进测试管理工具的每条用例的“路径编号”一栏建议保留方便后续做覆盖度回归比对。用例编号覆盖路径amountstockvipcouponUsedbalance预期 discount预期 payable预期输出TC-01P2010falsefalse0——参数异常TC-02P120110truetrue045110通过TC-03P330010falsetrue035195通过TC-04P430010truefalse030210通过TC-05P520010truetrue025150通过TC-06P630010truetrue20045−35拒绝几条用例的设计意图值得展开说。TC-01 用的是amount 0这是判定 1 复合条件里第一个条件触发的情形如果预算允许再加一条stock 0的用例覆盖第二个条件虽然路径相同但能验证短路行为是否符合预期。TC-02 的 amount 取 201刚好跨过判定 4 的门槛属于边界值分析顺手捎带的收益。TC-05 的 amount 取 200刚好不满足amount 200走的正是 P5判定 4 为假。TC-06 用 balance 200 把 payable 压成负数触发拒绝分支。提示预期 discount 这一列千万别省。只看预期输出的话TC-02 和 TC-06 都走了完整折扣链输出却不同靠输出对不上号有了中间值一眼就能判断路径对不对。这个习惯我是踩坑之后才养成的早期做的用例集一旦某条路径因为重构换了顺序输出没变但覆盖率掉下去了根本发现不了。4. 踩过的坑基本路径法最容易翻车的六个地方4.1 复合条件拆不拆影响的是用例数量这是新手最容易纠结的问题。if (amount 0 || stock 0)到底算一个判定还是两个我的实操结论是要看风险等级。如果这个复合条件只是参数校验即使漏掉stock 0也不会有严重后果那就当成一个判定圈复杂度少一用例少一条性价比高。如果这个复合条件涉及资金安全或数据一致性比如转账时的余额充足 未超出单日限额那就必须拆。拆的方法有两种一是在画图阶段把每个原子条件当独立判定圈复杂度直接涨二是不拆图但在用例设计阶段对这条判定额外做条件组合覆盖把四种组合都覆盖掉。还有个隐藏问题不同语言的短路求值行为不一样。多数语言里a || b在 a 为真时不会求值 b但有些场景下 b 表达式的求值本身有副作用比如函数调用、计数器递增这时候短路的差异会导致行为不一致。遇到带副作用的复合条件一律拆开单独验证别省这几条用例。4.2 不可行路径算法给出的路业务走不通不可行路径是基本路径法的固有缺陷任何教材都会提但真遇到的时候还是容易懵。它指的是从控制流图上看这条路径存在但实际上找不到任何一组输入能让它被执行。我遇到过一个典型案例代码长这样if (level 1) { a 1; } else if (level 2) { a 2; }。画成图以后从判定 2 的“真”分支和判定 3 的“真”分支理论上可以同时走通但业务上level 1和level 2互斥不可能同时成立所以那条组合路径是不可行的。按基线翻转法算你会得到 6 条独立路径但其中一条永远找不到输入数据。处理办法有三个层次。第一层先确认是不是真的不可行用参数求解试试很多时候你以为不可行其实是没找到合适的输入组合。第二层确认不可行后把这条路径从用例集中剔除在用例表里标注“不可行路径已分析”并把剔除原因写清楚比如“两个判定互斥”。这个过程本身就是有价值的——它证明了你的分析是完整的而不是漏了。第三层如果不可行路径占比很高说明代码结构有问题通常是把互斥条件写成了串联的 if-else-if可以考虑重构成switch或者查表法从源头减少不可行路径。经验不可行路径的数量可以和圈复杂度一起作为代码结构质量的指标。如果 V(G) 是 10 但有 4 条不可行说明代码里有大量互斥判定可读性和可测性都堪忧。4.3 循环只走一次还是零次第 2.4 节提过基本路径法对循环的默认假设是循环体最多执行一次。这个假设在实际项目里帮我省了大量用例但代价是有几类缺陷它抓不到。最容易漏的是循环变量的边界错误。比如for (int i 0; i list.size() - 1; i)这个 off-by-one 错误在“循环体执行一次”的假设下完全测不出来必须用 list 只有一个元素、两个元素这样的边界用例去打。还有循环条件恒真导致的死循环这个在路径分析阶段也看不出来。另一个高频坑是累加器初始化。我有次遇到一个方法内部有个int total声明在方法外静态变量循环里累加。单次调用完全正常第二次调用结果就翻倍了。这类缺陷路径覆盖一点办法都没有只能靠多次调用同一方法、或者用状态相关的场景用例去覆盖。所以我在做路径分析的时候会额外扫一眼代码里有没有跨调用的可变状态有就单独加用例。4.4 常见问题速查表下面这张表是我带新人时整理的基本都是高频问题。现象可能原因排查与解决三种方法算出的 V(G) 不一致节点或边的数量数错合流点没算重画图重点检查 if-else 汇合处和 return 语句独立路径数多于圈复杂度引入了非独立路径重复走了同一方向检查每条路径是否引入新边翻转时找不到输入数据命中不可行路径求解约束确认互斥后标注剔除用例全通过但覆盖率没涨路径设计对了但断言只看了输出值补上关键中间变量的断言覆盖率工具报的分支覆盖率高但路径覆盖低工具默认统计分支覆盖不统计路径手工核对独立路径清单或用支持路径覆盖的工具复合条件判定内部缺陷测不出短路求值掩盖了单条件问题拆开复合条件或补条件组合用例循环相关的错误抓不到循环只按进出分析补边界用例空集合、单元素、上限值重构后用例还能过但覆盖下降用例与代码行绑定路径变了用例表保留路径编号重构后重新比对路径5. 把它塞进日常工程流程的几种做法5.1 覆盖率工具的正确读法覆盖率工具一定要用但千万别被那个百分比骗了。Java 生态里 JaCoCo 是标配配合 IDE 的 EclEmma 插件能直接看到行级和分支级的覆盖高亮C/C 用 gcov lcov嵌入式项目里特别常见因为交叉编译后能直接跑在目标板上Python 用 coverage.pyWeb 前端用 Istanbul/nyc。关键在于理解工具报的是什么指标。JaCoCo 的“分支覆盖率”branch coverage统计的是每个判定节点的真假方向有没有都走到它不等于路径覆盖。也就是说分支覆盖率 100% 的代码路径可能只走到了一半。举个极端的例子两个并排的 if 语句四个分支都被覆盖过但四条路径TT、TF、FT、FF可能只走了两条。正确读法是把分支覆盖率当成必要不充分条件达到 100% 只是及格线。真正做路径覆盖核查还是要靠前面那张独立路径清单手工比对。我在项目里的做法是核心模块上线前必须过一遍路径清单覆盖率报告只用来发现“哪些分支根本没被任何用例碰过”属于快速筛查工具。顺便说一个实测细节覆盖率数字一定要看三次以上。单次跑出来的数字受测试顺序影响很大加个随机种子多跑几轮有些分支覆盖率会掉下来说明之前是被别的用例“顺带”覆盖的并不是真的有对应用例这种虚假覆盖在回归时最容易暴露。5.2 与等价类、边界值、场景法的配合姿势单独用基本路径法的效果一般它是组合拳里的一环。我的标准流程是四步走。第一步用等价类和边界值把输入域切好。每个参数的有效类、无效类、边界点列成表这部分和路径无关纯粹是数据维度。第二步用基本路径覆盖法导出独立路径确定逻辑维度需要多少条用例。第三步把两者做笛卡尔积的裁剪版——路径决定“走哪条路”等价类决定“用哪组数据走这条路”每条路径至少配一组有效类数据高风险路径再补无效类和边界值。第四步用场景法把单函数用例串成端到端流程处理跨模块的状态依赖。举个具体例子。上面那个 auditOrder如果只做路径覆盖TC-01 用 amount 0 就够了。但加了边界值之后我会补一条 amount 1判定 1 为假的最小值和 stock 1 的用例因为 0 和 1 之间是最容易出整数溢出和下溢的地方。再比如 TC-05 的 amount 取 200边界值提醒我补一条 201这两条一起就把判定 4 的门槛卡死了。这种配合带来的用例增量很小但缺陷捕获率提升明显。5.3 AI 生成用例之后用它做一次结构性兜底这两年用 AI 辅助生成功能测试用例的做法很流行输入一份 PRD 或者接口定义直接让它吐用例效率确实高。但我在实际用的时候发现一个规律AI 生成的用例善于覆盖业务规则弱于覆盖逻辑通路。原因不难理解AI 看到的是需求描述需求里通常写的是“VIP 打九折”“用券再减 15 元”这些是规则它能把规则组合写成用例但它不知道代码里这些规则是用串联的 if 实现的还是查表实现的也就无法保证代码里的每一条独立路径被走到。所以我的做法是分层。AI 生成的用例作为业务维度的输入负责覆盖需求里的每条规则组合基本路径覆盖法作为结构维度的兜底负责核对代码里的独立路径有没有全被走到。两边的用例合在一起再用覆盖率工具跑一遍看有没有分支是两边都没碰到的。跑出空分支就说明需求描述和代码实现之间有偏差这本身就是个值得追查的发现——要么是需求漏写了要么是代码写了需求外的东西。同样的思路也适用于代码评审和自动化测试生成。用 AI 自动写测试脚本的时候让它按路径清单逐条生成比丢一句“帮我给这个函数写测试”效果好得多因为路径清单已经给了它明确的结构约束。5.4 面试与比赛里怎么讲清楚基本路径覆盖法是软件测试面试和各类测试大赛的高频考点。面试官问“圈复杂度怎么算”的时候别只背公式把三种算法都说出来再补一句“三个结果必须一致不一致就是图画错了”这一句就是从业者和背书者的分水岭。更常见的问题是“路径覆盖和分支覆盖的区别”。标准答案是分支覆盖要求每个判定的真假都出现路径覆盖要求每条线性无关路径被执行分支覆盖是路径覆盖的必要条件。但更好的回答是举例子——两个独立 if 的结构分支覆盖只要两组数据路径覆盖需要三组因为 FF 和 TT 加上一条混合路径才能保证线性无关。能现场举例的人面试官基本就放心了。还有一个高频追问是“不可行路径怎么办”。这个问题的答案没有标准模板看你有没有真实踩过坑。我的回答框架是先尝试求解确认再标注剔除并说明原因最后反思代码结构是否需要调整。如果面试官继续追问“那你的覆盖率是不是就不够了”可以回应说覆盖率指标本身要通过用例执行来度量不可行路径不占用测试资源反而说明分析到位了关键是把剔除过程文档化让评审能追溯。真到动手写用例的时候还有个细节值得注意把路径编号写进用例的备注字段。我早期不写结果代码重构之后用例还全绿但覆盖率悄悄掉了一截谁都说不清哪条路径丢了。写上路径编号以后重构完先比对路径清单哪些路径消失了、哪些新增了一目了然用例维护的工作量能砍掉一大半。这个习惯看着小省下的时间是真的。
返回列表