
开始聊聊因果图法在自动售货机上的实战做测试这些年经常被问到“黑盒测试里那么多方法到底哪个最好用”。说实话没有最好用只有最合适。等价类和边界值能搞定大部分独立输入的场景但一旦碰到“多个条件组合起来才决定一个结果”的业务逻辑它们的覆盖能力就不够看了。这时候因果图法就该上场了。我第一次真正用因果图法解决实际问题就是在一个自动售货机的项目上。那会儿项目组对出货逻辑的要求特别多投币、选货、出货、找零、还有各种异常情况。一开始用等价类划分和边界值分析去设计用例结果遗漏了好几个组合场景差点上线翻车。后来老老实实用因果图法梳理了一遍才算把整个逻辑吃得透透的。这篇文章就围绕自动售货机这个问题把我用因果图法的完整思路、绘图步骤、判定表生成、用例设计技巧以及过程中踩过的坑全部摊开来讲。这个案例特别适合刚入门测试、或者接触了因果图法但对实际应用还没什么手感的读者。自动售货机本身的需求不复杂但逻辑组合不少刚好能把因果图法的价值体现得明明白白。看完之后你不仅能搞懂因果图法怎么画、怎么用更重要的是能明白它背后的“为什么”——为什么它比凭经验乱猜的组合测试更靠谱。1. 因果图法到底解决什么问题1.1 黑盒测试里的“组合爆炸”困境黑盒测试里我们不关心程序内部是怎么实现的只关注输入和输出的关系。理论上把所有输入条件的所有组合都测一遍是最稳妥的。但现实很骨感就拿自动售货机来说假设投币的面值有1元、5元、10元三种商品价格有3元、5元、8元三种光是“投币-选货”的组合就有3×39种。再考虑一下“钱够不够”“有没有零钱可找”“商品是否售罄”这些附加条件组合数量蹭蹭往上涨。在实际测试项目里我们不可能把所有组合都跑一遍。一方面是时间不够另一方面很多组合其实是无效的、冗余的。比如自动售货机的业务规则里“投币5元选择3元商品”和“投币10元选择8元商品”在某些条件下得经过同样的中间判断它们的等价性很高。因果图法要做的就是从复杂的业务需求里提炼出“原因”和“结果”再通过逻辑关系把它们连接起来最终生成一份精简但覆盖全面的测试用例集合。1.2 为什么选择因果图法而不是其他黑盒方法等价类划分把输入域划分成若干有效和无效的类适用于单输入条件的验证。边界值分析关注的是边界附近的取值解决的是“临界点容易出错”的问题。但它们有一个共同的短板默认输入条件之间是相互独立的。可是真实业务系统里条件之间经常是有依赖的、有组合的。比如自动售货机里“找零”这个结果不是单独由“投币金额”决定的而是由“投币金额大于商品价格”和“有足够零钱”这两个条件同时决定的。用等价类和边界值你得分别测什么投币金额边界、商品价格边界……可一旦组合起来就不知道怎么设计了。这时候因果图法的优势就出来了先画图理清逻辑再转成判定表最后生成用例整个过程有章可循测试覆盖率的可信度也更高。1.3 因果图法与判定表的关系很多人把因果图法和判定表当成两个独立的方法其实它们是一对搭档。因果图是定性分析工具帮我们看清各个条件和结果之间的逻辑关系判定表是定量表达工具把因果图中的组合关系系统化地列出来每一列就是一种条件组合及其对应的动作。从因果图到判定表几乎是水到渠成的事。我个人的实操习惯是先根据需求画因果图然后用因果图辅助生成判定表再来从判定表提取测试用例。这样每一步都有产出物评审起来也方便。2. 自动售货机需求分析与因果图拆解2.1 自动售货机的核心需求建模我们先把自动售货机的问题做一个简化定义售货机里有一种价格为3元的商品机器接受1元、5元和10元的纸币或硬币。操作流程是顾客投币选择商品机器判断是否有足够的金额购买如果钱够则出货如果有找零则找零如果钱不够则提示继续投币或退币。从这笔需求里我能提炼出几个核心因素投入的金额C11元、5元、10元是否选择商品C2选或者不选投入金额是否大于等于商品价格3元C3机器内是否有足够的零钱可以找零C4商品是否有货C5。这些都是“因”。对应的“果”有E1成功出货E2提示余额不足E3提示商品售罄E4退还多余金额找零E5退还全部投入金额退币。这里要注意C3是由C1派生出来的一个中间条件不是顾客直接输入的条件但它对结果的影响非常大。因果图法里管这种叫“中间状态”画图的时候一定要体现出来因为它能帮我们把复杂逻辑拆成几个小的子模块逐个分析。2.2 因果图符号与画法速成如果你还不太熟悉因果图的画法我先简单科普一下最常用的几个符号。因果图里有四种基本逻辑关系恒等原因出现结果就出现原因不出现结果就不出现。也就是“如果a那么b”。非原因出现结果不出现原因不出现结果出现。也就是“如果a那么非b”。或多个原因中只要有一个出现结果就出现。用符号表示是“∨”。与多个原因同时出现结果才出现。用符号表示是“∧”。除了原因和结果之间的逻辑关系原因与原因之间还有约束关系。比如自动售货机里投入的金额不可能同时是1元和5元这就是“异”约束EExclusive也叫互斥。某些系统里还有“包含”约束I、“唯一”约束O、“要求”约束R和“屏蔽”约束M。画图的时候这些约束都要标注清楚否则后续判定表列条件组合的时候容易出错。2.3 自动售货机的因果图绘制实操基于上面的需求我画出完整的因果图。图片没法直接贴在文章里但我会把它拆成几个关键的局部逻辑第一组逻辑是关于“能否出货”的。出货E1是由两个中间条件共同决定的一是投入金额足够C3为真二是商品有货C5为真。也就是说E1 C3 ∧ C5。第二组逻辑是关于“是否提示余额不足”的。这个结果比较简单只要投入金额小于商品价格C3为假不管有没有选商品机器都会提示余额不足。所以 E2 ¬C3。第三组逻辑是“是否找零”。找零E4的前提是投入金额大于商品价格C3为真且商品有货C5为真同时机器里有足够的零钱C4为真。注意啦如果商品没货就算投了钱机器只会退币而不会找零因为交易根本就没成立。所以 E4 C3 ∧ C5 ∧ C4。第四组逻辑是“退币”。退币E5发生在两种情况一是商品没货顾客投了币机器无法完成交易原路退回二是投入金额不足顾客选择退币。这两条路径都要汇总到E5上用“或”的关系把它们连接起来。中间层的逻辑关系是最容易画乱的。我一开始画的时候把C3、C5的与逻辑直接画到了E1上又在另一个分支画了E4结果图面交叉线特别多评审的时候自己都看得有点晕。后来学乖了先把所有原因列在左边结果列在右边中间用“中间结点”承接分块梳理最后再合并。这样图面干净逻辑也清楚。3. 从因果图生成判定表的完整过程3.1 判定表的构成与设计步骤判定表由四个部分组成条件桩、动作桩、条件项和动作项。条件桩列出所有的原因输入条件动作桩列出所有的结果输出动作条件项是各种条件取值的组合动作项则是在对应条件组合下应该执行的动作。从因果图生成判定表的步骤大概是这样的列出所有原因和结果确定原因的数量n理论上会有2的n次方种条件组合逐一把每种组合代入因果图推导出对应的结果合并结果相同的列这一步很关键能大幅精简表格每一列对应一条测试用例。3.2 自动售货机判定表的生成过程回到自动售货机的例子。为了不让表格膨胀得过于夸张我先把原因做适当归并。真正画判定表的时候我不会把“投入1元、5元、10元”拆成三个完全独立的布尔条件因为它们是互斥的。我会用一个条件项来表示“投入金额类型”取值分别是1元、5元、10元。假设我选取以下条件A投入金额1元 / 5元 / 10元B是否选择商品是 / 否C商品是否有货有 / 无D机器是否有足够零钱有 / 无动作有四个X1正常出货X2提示余额不足X3提示售罄并退款X4正常出货并找零把条件组合展开我们会得到 3×2×2×224 种组合。24种还不算多可以根据业务规则筛选无效列并把相同结果的列合并。举个例子当投入金额是1元时金额必然少于3元这时候不管其他条件怎么样结果都只能是“提示余额不足”。这一批列就可以合并成一条测试用例。合并之后最后的判定表大概只有十列左右特别清爽。3.3 判定表中的无效条件组合处理处理无效组合是判断一个测试人员有没有经验的分水岭。新人拿到需求往往会把所有组合都列出来结果表格里出现了一堆“不合法”的列比如“商品无货”和“正常出货”同时为真这在逻辑上是成立的商品无货但扣款出货系统会出bug的但在业务规则里如果不允许就要做标记。对于判定表里的无效列我的处理原则是不直接删除而是单独标注为“无效组合”在生成测试用例时不作为正向用例但可以保留下来作为异常场景的参考。有时候系统恰恰在这种“理论上不该发生”的情况下出了bug保留这些记录以后回归测试还能派上用场。4. 完整实操过程与测试用例设计4.1 测试环境准备测试环境的搭建其实不复杂。如果被测对象是真实的自动售货机那么需要准备不同面额的真币或代币若干不同价格的商品若干以及一个能观察机器内部状态的管理员视角。如果被测对象是一个Web端或App端的模拟售货机软件那只需要准备测试账号、商品数据、优惠券配置等等。我当时做的是软件模拟端所以准备的重点在数据上。我提前向开发要了一份“商品价格配置表”确认了商品价格是3元又准备了1元、5元、10元三种面额的测试数据。这里有个特别容易忽略的点真实的自动售货机对纸币的新旧、褶皱程度是有感应的但软件模拟端只管金额数值所以我要确保测试数据里面额和数值的对应关系是完全准确的不能出现“投了5元系统记成10元”这种基础性错误。4.2 用判定表逐列生成测试用例拿判定表的每一列来生成测试用例是标准做法。我把最终判定表的列选出来给每个列编号比如TC-01、TC-02……然后把条件项映射成具体的输入操作把动作项映射成预期的输出结果。举个例子判定表里有一列的条件是“投入金额5元选择商品是商品有货有零钱有”对应的动作是“正常出货并找零2元”。那么这条测试用例的步骤就是在售货机上投入5元选择价格为3元的商品观察机器是否出货观察找零口是否吐出2元。预期结果是出货口掉落3元商品找零口吐出2元硬币。类似的还有“投入1元不选择商品商品有货有零钱无”这一列预期结果是“机器提示余额不足”等待顾客补充金额或选择退币。把判定表里的有效列全部转换成用例之后我再补充一些判定表之外的用例比如连续购买两次、第一次购买未取货就进行第二次购买还有投币过程中突然断电模拟异常中断等场景。这些用例在因果图里不好表达但实际测试里非常重要不能漏。4.3 用结果回溯验证用例覆盖度一个容易被忽略的步骤是覆盖度验证。我在设计完用例之后会做一次“结果回溯”把每条测试用例的预期结果列出来看看所有结果动作项是否都至少被一条用例覆盖到了。比如“提示余额不足”这个结果我检查了一遍发现TC-03、TC-07这些用例里都有覆盖但“提示售罄并退款”这个结果最初在合并判定表的时候被并掉了只保留了“商品有货无”的少量几条。回溯之后我立刻补了一条“投入5元选择商品商品无货零钱充足”的用例确保“售罄退款”的逻辑在零钱充足和零钱不足两种情况下都被验证到。这种回溯习惯看起来只花几分钟但能避免很多“结果路径没被覆盖”的隐性漏测。5. 常见问题、排查技巧与实操心得5.1 常见问题速查表问题现象可能原因排查思路因果图画完很乱自己都看不懂原因、结果、中间节点没有分层重新梳理把中间结点单独提取先画局部再合并判定表列数太多膨胀严重没有合并相同结果列按结果对列进行标记和合并先找规律再合并用例覆盖了多个条件但结果断言不精确预期结果写成了模糊描述每条用例预期结果必须具体到“出货找零X元提示音”等可执行的动作结果回溯时发现某个动作无用例覆盖因果图漏画了该路径回到因果图检查逻辑是否存在分支遗漏补充用例实际机器出现多找零或不出货零钱判断逻辑与找零算法冲突联合开发查看源码重点排查条件组合处是否缺少“零钱足够”判断5.2 我踩过的几个坑自动售货机这个案例虽然简单但我当初踩的坑可一点都不少。第一个坑是画因果图时把“投币1元”和“投币5元”当成两个完全独立的原因但实际上它们在一个售货行为里是互斥的一个人不可能同时投1元和5元。如果没有在因果图上标注“异”约束判定表里就会出现“投币1元且投币5元”这种现实中根本不存在的组合白白浪费用例条数。第二个坑是找零逻辑。我最初画的因果关系是“投入金额大于商品价格就找零”忽略了“机器里有没有足够的零钱”这个先决条件。后来在评审时被开发提醒如果机器里全是各种币值的硬币但没有1元硬币恰好需要找零1元那机器只能退币或者提示无法找零。这个细节直接导致我多补了两条关键用例。第三个坑是中间结点的使用不当。一开始我把所有判断条件一股脑全连到最终结果上导致图面交叉得很严重图上多了很多线条。后来改成“先局部中间判断再合成最终结果”的方式整个图才清晰起来。这也影响到后面判定表的生成因为中间结点能帮我把大逻辑拆成一组小组的子逻辑逐个子逻辑判定表的列数少很多合并起来不费劲。5.3 实操心得因果图法让测试不再靠想象力很多测试新人做组合场景测试时习惯用“我觉得应该这样”“我猜系统会那样”的思维方式其实这非常危险。黑盒测试的价值在于用可重复的方法从需求里推导出完整的用例集合而不是靠某个人拍脑袋。因果图法就是帮我们把需求里的“因为……所以……”的隐含逻辑显式化变成一张谁都看得懂的图再变成一份谁都能照着执行的用例。我在自动售货机的项目里靠因果图法把原本可能漏掉的“投入金额不足但商品恰好有货并选择商品”的路径也补上了。当时开发同事看完我给出的用例直接说了一句“这系统的逻辑边界被你们摸得很清楚嘛。”那一瞬间我觉得这个方法用得值。最后再分享一个小技巧如果你在一个复杂系统里条件数超过了五个、结果超过三个强烈建议先用因果图梳理逻辑再转判定表。不要试图跳图直接画判定表没有逻辑关系的梳理判定表就是一个庞大的笛卡尔积看着就头大。因果图的作用就是在一堆纷繁复杂的业务规则里帮你找到那条最清晰的逻辑主线。