☰
因果图法实战:从需求分析到决策表,搞定黑盒测试组合场景
2026/10/2 20:21:25 网站建设 项目流程

每个做软件测试的人,迟早都会遇到这么一类bug:单独测试每个功能都正常,一旦把多个输入条件组合起来,就会出现莫名其妙的问题。我刚入行那会儿,被一个“只有特定选项组合下才会触发”的缺陷折磨了两天,排查到最后才发现是需求文档里写了一个非常隐蔽的条件分支。从那之后,因果图法这个名字在我心里的分量就不一样了——它不是面试八股文里背一背的术语,而是一套能真正把“组合爆炸”问题梳理清楚的黑盒测试设计方法。无论是做功能测试、接口测试,还是写自动化用例前的场景设计,因果图法都能帮你在动手执行之前,把逻辑关系和测试范围理得明明白白。

这篇文章,我就把自己从理论到实战用因果图法的经验完整拆开来。包括它解决什么问题、符号和约束怎么用、从需求文档到因果图再到决策表的完整链路,以及我在真实项目里踩过的一些坑和搭配其他测试方法的心得。如果你正在备考软件测试面试,或者刚接手一个条件判断比较多的模块,这篇文章应该能让你少走不少弯路。

1. 为什么我劝你别小看因果图法:它堵住的是组合场景这个最大漏网之鱼

测试设计的方法其实不少:等价类划分解决“用最少的数据覆盖尽可能多的类型”,边界值分析解决“在边界附近最容易出错”的问题,场景法解决“按用户实际操作路径来测”的问题。但有一类问题,上述方法都不好直接解决——多个条件之间的组合关系。举个最简单的例子:一个登录功能,用户名和密码都要校验,登录是否成功还取决于账号状态是否正常。这三个条件各自有“合法/非法”两种状态,如果不懂组合设计,你可能会凭感觉挑几组数据来测。但用户实际使用中是会把各种情况拼在一起的,漏掉一组组合,很可能就把一个bug漏到了线上。

因果图法就是为这种场景设计的。它的核心思想很朴素:把需求里所有的输入条件当作“因”,把所有的输出结果当作“果”,然后分析它们之间的逻辑关系,把这个关系画成一张清晰的图。有了这张图之后,再把它转换成一张规则表,从规则表里导出最终要执行的测试用例。用大白话说,它就是把“如果……那么……”这种散落在需求文档里的逻辑,变成一步一步可推导、可验证的测试设计过程。

我第一次被因果图法“救”回来,是在测一个电商优惠券模块。那个需求里有满减、打折、包邮、会员专享等等一堆规则叠加在一起,当时我头都大了。后来我按照因果图法的思路,把每条规则都当成一个输入条件,把用户最终看到的实付金额和是否包邮当作输出结果,一步一步把关系图画出来,再转成规则表,整个理路瞬间就清晰了。那一轮测试,我不仅把组合场景覆盖得很全,还反过来发现了需求文档里两个规则互相矛盾的地方。从那个时候起,我就特别笃定:凡是有多个条件互相作用的功能,因果图法就是那面最该挂上的安全网。

2. 因果图法的建模语言:从“因”与“果”到四种逻辑符号和五类约束

想真正用起来因果图法,不能光记住“原因”“结果”这两个概念,还得掌握它的符号系统和约束规则。这部分看似简单,却是很多人画图画到一半就乱套的根源。

2.1 原因、结果与中间节点:先把需求拆成最小的逻辑单元

因果图法把系统的外部输入条件称为“原因”,把系统的输出或状态变化称为“结果”。在实际分析时,一个“原因”可以是一个输入项是否合法,也可以是一个开关是否打开,甚至可以是一个外部事件是否发生;一个“结果”可以是界面上出现某条提示,也可以是数据库里写入某条记录,还可以是跳转到某个页面。关键是,原因和结果都要是可以明确判断真假的命题。

有些复杂的逻辑还会出现“中间节点”。中间节点既不是什么真正的输入,也不是最终输出,而是组合了多个原因之后形成的中间状态。打个比方,你判断“是否允许登录”这个结果时,会先判断“验证码正确且账号未被锁定”这个中间条件,而这个中间条件又由两个更基础的原因构成。中间节点的作用是让大图不至于一下子变得太复杂,画图的时候可以分层去表达。

2.2 四种逻辑符号,对应需求里最常见的几类关系

因果图法里的逻辑关系符号并不多,就四种:恒等、非、或、与。不过它们组合起来能表达的逻辑很丰富。

恒等关系就是“原因出现,结果就出现”。比如条件“按下提交按钮”,结果“表单被提交”,这就是一条恒等关系。

非关系表示“原因不出现时,结果才出现”。最典型的是各种“当输入不合法时,给出错误提示”,这里的结果和那个“不合法”之间就是非的关系。

或关系表示“多个原因中任意一个成立,结果就成立”。比如“用户名或密码为空,提示登录失败”,两个原因之间是或的关系。

与关系表示“多个原因同时成立,结果才成立”。比如“手机号正确且验证码正确且账号未冻结,允许登录”,这里就是典型的多条件与关系。

画图时,这些符号一般用带标记的连线来表示,常见的是在连接线上标字母或用逻辑门图形表达。但在实际工作中,真不一定非要画出多么标准的图形,重点是把你理解到的逻辑结构准确地可视化出来。有些团队在评审测试方案时,用文字加上简单的逻辑表达式反而沟通起来更高效。

2.3 五类约束:用来表达现实世界里不成立的条件组合

光有逻辑符号还不够,因为真实业务里,很多原因之间是互斥的、包含的、或者有依赖关系的。这些现实世界的限制,单靠逻辑运算符表达不了,需要用到约束。

我按自己理解整理了一下,这五类约束可以这样记:

  • E(互斥,Exclusive):多个原因中至多只能有一个成立。比如下拉框里选了“个人用户”就不可能同时选“企业用户”。
  • I(同或,Inclusive):多个原因中至少有一个成立。比如支付方式“微信支付”“支付宝支付”“银行卡支付”里,至少要选一种。
  • O(唯一,One):多个原因中有且仅有一个成立。比如单选项里“男”和“女”,必须且只能选一个。
  • R(要求,Require):一个原因成立时,要求另一个原因也成立。比如选择了“企业认证”,就要求填写“企业名称”。
  • M(屏蔽,Mask):一个原因成立时,另一个原因就不允许出现。比如“不再显示该弹窗”一旦开启,那么“显示欢迎弹窗”的条件就被屏蔽掉。

约束是整个因果图法里我最喜欢也最怕的一部分。喜欢它,是因为它能替你做减法,把大量不可能会发生的条件组合直接剪掉,规则表的规模一下就降下来了;怕它,是因为如果分析需求的时候漏掉了约束,画出来的图虽然形式上没问题,但导出的测试用例会包含一堆现实中根本不存在的场景,白白浪费执行时间。

3. 从需求文档到因果图的完整推演:一个自动售卖机案例的逐步拆解

这一节我通过一个相对完整的案例,带大家走一遍从需求文本到因果图的流程。这里我选一个自动售卖机的购买流程,原因很简单:它条件清楚、结果可控,而且贴近日常,没有任何行业门槛。

3.1 需求段落:先把原始需求内容原样摆出来

假设需求文档里是这么写的:

“用户可以向自动售卖机投入硬币并选择商品。当投入的总金额达到或超过商品价格时,可以购买该商品;当投入金额不足时,提示‘余额不足’,并保持等待投币状态。商品售出后,如果有多余的钱,则进行找零;如果没有多余的钱,不找零。如果用户选择取消购买,则退回所有已投入的金额。”

先别急着画图,测试用例设计的第一步永远是“吃透需求”。把这段文字读三遍之后,我会带着两包不同颜色的荧光笔去标注,一包标“原因”相关的词,一包标“结果”相关的词。

3.2 识别原因、结果与中间状态:标注是画好图的前提

我一般会从这句话里的动词和判断词入手。原因通常是“用户做了什么”“系统接收到什么条件”,结果通常是“系统给出什么反馈”“系统进入什么状态”。

从这个需求里,我可以识别出这样几个原因:

  • 原因C1:用户投入了硬币。
  • 原因C2:用户选择了商品。
  • 原因C3:投入总金额大于等于商品价格。
  • 原因C4:投入总金额小于商品价格。
  • 原因C5:用户点击取消购买。

也许有朋友会疑惑,C3和C4不是刚好相反吗?为什么都当成原因列出来?这里要把“原因”理解成“系统在每个判断节点上要检查的条件”,而不是最终用户输入的那个动作。用户输入的只有投币、选商品、取消,但系统在判断时,要分别检查“钱够不够”和“钱不够”这两个分支,所以它们都可以是图中的原因节点,前提是它们之间要加上互斥约束。

再看结果:

  • 结果E1:提示“余额不足”,保持等待投币状态。
  • 结果E2:售出商品。
  • 结果E3:找零。
  • 结果E4:不找零。
  • 结果E5:退回所有已投入金额。

除此之外,还有一个中间状态值得拎出来:投入金额是否达到商品价格。这个中间状态可以由C3或C4来体现,可以直接用,也可以设成一个单独的中间节点“金额充分性”,让图更清晰。我倾向于保留C3、C4作为原因,不再额外加中间节点,因为这里的关系已经足够直白。

3.3 画出逻辑关系并补充约束:让这张图反映真实世界

现在开始连线。

先说最简单的部分:当C1、C2、C3三个原因同时成立时,系统会售出商品,这就形成了一条与关系指向E2。

在E2成立的基础上,如果投入金额大于价格,就找零;等于价格,就不找零。但需求原文里没有单独把“投入金额大于价格”和“投入金额等于价格”分开列出来,这其实是个容易埋坑的地方。正确做法是拆成两个条件:原因C6“投入总金额大于商品价格”和原因C7“投入总金额等于商品价格”。再加上原来的C3“投入总金额大于等于商品价格”,你会发现它们之间是有包含关系的。这时约束就该上场了,C3和C4之间是互斥的,C6和C7之间也是互斥的。C3成立时,C6和C7二者必居其一。

继续说因果链路:C6成立且E2成立,则结果E3找零;C7成立且E2成立,则结果E4不找零。而C1、C2、C4同时成立时,说明用户选了商品但钱不够,则结果E1提示余额不足。C5一旦成立,无论当前处于什么状态,都会触发结果E5退回全部金额。这里C5和C2之间其实存在实际业务上的约束:用户只有在选完商品之后才可能看到取消按钮,但需求里没有说明,所以我标一个“M屏蔽”约束,表示C5成立时C2的后续购买流程不允许继续输出售货结果。

这张图画完之后,再回头看一遍需求原文,我立刻发现了一个潜在矛盾:当用户选择取消购买时,如果此时投入金额已经等于商品价格,“是否应该先退币再取消”在原文里并没有说清楚。这就是画因果图的价值——它逼着你去澄清需求,而不是等到测试执行了半天才发现理解有歧义。

3.4 常见卡壳点:图越画越乱的三个原因

很多初学者画到一半就开始烦躁,通常是因为下面三个问题。

第一,把“原因”理解得太窄。有人只把用户操作当原因,忘了系统内部的判断条件也是原因。其实只要是逻辑判断的分支入口,都可以作为原因节点。第二,边界条件没有拆细。比如“金额大于等于价格”这种条件,如果不在因果图阶段拆成“大于”和“等于”,后面推导测试用例时就很可能漏掉“刚好等于价格”这个最容易出错的数据点。第三,约束漏标。漏标约束的直接后果就是决策表里多出一堆无意义的规则,让用例数量虚胖。

4. 用决策表搭桥:把因果图转化成可执行测试用例的关键工程

因果图画得再漂亮,也不能直接拿去执行测试。它的下一步通常要从一个二维表里走一遍,这就是决策表,也叫判定表。整个过程我觉得特别像把“电路图”转成“真值表”——逻辑图表达的是关系,表格表达的是可枚举的规则。

4.1 决策表的基本结构:条件桩、动作桩和规则列

决策表一般分成四个区域:条件桩区、动作桩区、条件项区、动作项区。条件桩是列出所有原因,动作桩是列出所有结果,条件项是每个原因在当前规则列下的取值(成立或不成立),动作项是每种条件组合下对应的结果是否触发。

把因果图转成决策表的步骤是这样的:列出全部原因放到条件桩,列出全部结果放到动作桩,然后枚举所有符合约束的条件组合,逐列填充条件项,再根据因果图的逻辑关系推导出每个结果是否成立,填入动作项。每一列就代表一条决策规则,指向一组可执行的测试用例。

这么做的好处是怎么强调都不过分:它让“为什么设计这些用例”这件事变得可追溯。评审时别人问你“这几个用例是怎么来的”,你直接甩出决策表,逻辑链路一眼就能看清,比嘴上解释半天管用太多。

4.2 从理论上限到实际规则:组合爆炸怎么被约束压下来

不夸张地说,因果图如果不加约束直接转决策表,规则的规模会非常吓人。假设有5个原因,每个原因有“成立”和“不成立”两种取值,那么完整的组合就是2的5次方,等于32条规则;如果有10个原因,就是1024条;20个原因,就是一百多万条。如果真到那一步,因果图法的优势就彻底变成了灾难。

所以约束的作用必须被充分利用。还是拿售卖机案例来说,C3和C4互斥,C6和C7互斥,这就直接把一大半永远不会出现的组合从决策表里删掉了。再加上C5对后续流程的屏蔽关系,最终的规则数会大幅缩水。实践中我通常遵循一条原则:先加约束剪枝,再检查剪掉之后是否仍有某个关键分支没有被覆盖到。千万不要为了省用例数量,把不该剪的给剪了。

4.3 从规则列到测试用例:每一条规则都不是只能生成一条用例

很多教程讲到决策表就停了,好像把规则列出来就等于用例设计完了。但实际落地的时候,还需要再往前走一步。一条规则往往只描述了“某个条件成立/不成立”这样的事实,可测试执行是需要具体数据的。

举例来说,规则里写着“投入金额小于价格——余额不足”,真正执行测试时,你要造一个具体的小于价格的数据,比如商品价格3元,我投了2元9角;还要检查此时是停留在等待投币状态,还是系统直接退币。另外,数据本身还可能有边界,比如“小于”包含1分钱差额,也包含几乎等于价格的差额,这两种情况在实现时往往走的不是同一条代码路径。所以我的习惯是从一条规则出发,再结合边界值分析法补充具体的数据取值,让同一个规则最终生成一或多条真正可执行的测试用例。

顺带说一下,决策表本身还有一个好处是能检查完整性。把规则列完之后,我会对着原需求逐条核对一遍,看看有没有哪个“结果”在表格里完全找不到出发路径。如果有,那多半是因果图阶段漏分析了。

5. 没有中间节点就不好用?复杂登录业务的因果图实战演示

前面那个售卖机案例虽然完整,但很多朋友看完可能还是觉得“这不就是个简单逻辑嘛,凭脑子想也能想到”。没问题,这一节上一个复杂度明显更高的例子:一个带多条件风控的登录认证流程。这是我在实际项目里处理过的业务形态,不是教科书里的玩具案例。

5.1 一段真实风格的登录需求:多个条件互相纠缠

假设产品经理给出的需求是这样的:

“用户登录时,需要输入手机号和密码。手机号必须为11位且以1开头;密码长度必须在8到20位之间,且必须同时包含字母和数字。如果手机号非法,提示‘手机号格式错误’,不再校验密码;如果手机号合法但密码非法,提示‘密码格式错误’;如果手机号和密码都合法,但账号在风控系统中被标记为风险账号,提示‘账号异常,请联系客服’;如果账号正常,则登录成功并跳转到首页。连续输错密码5次后,账号锁定,提示‘账号已锁定’。”

读完这需求,你第一反应是什么?我当时的反应是:如果用等价类划分硬做,我可能会把手机号、密码、风控状态、锁定状态都分别测一遍,但是“手机号合法,密码合法,风控正常,但由于连续输错被锁定”这种组合,很可能就漏掉了。

5.2 因果图和约束设定:分清层次,让图别失控

面对这种多条件的场景,我习惯先列出条件及约束:

  • 原因C1:手机号格式合法(11位,以1开头)。
  • 原因C2:密码格式合法(长度8-20,含字母和数字)。
  • 原因C3:账号处于风控风险名单。
  • 原因C4:账号已锁定(由连续输错5次触发)。
  • 原因C5:用户不是首次登录(涉及到某些产品还会区分新老用户,这里先不加,避免把案例撑太胖)。

结果:

  • 结果E1:提示“手机号格式错误”。
  • 结果E2:提示“密码格式错误”。
  • 结果E3:提示“账号异常,请联系客服”。
  • 结果E4:提示“账号已锁定”。
  • 结果E5:登录成功并跳转首页。

中间节点这里就有必要出现了。原因C1和C2没问题时,才会进入下一步的账号状态判断,所以可以设一个中间节点M1,表示“基础校验通过”。M1 = C1 与 C2。C3和C4都是账号状态层面的条件,它们和M1的关系是“M1成立时,才进一步判断C3和C4”。C3一成立,直接走E3;C4一成立,直接走E4;C3、C4都不成立,走E5。

约束方面,C3和C4理论上是可以同时成立的——一个账号可能既在风控名单里,又被锁定了。需求里没有说这种情况下优先级是什么,这又是一个测试人员必须主动提的问题。在实际项目里我通常会拉上产品经理确认优先级,而在测试设计时,可以把C3和C4同时成立作为一条单独的危险组合规则放进决策表,然后把产品经理最终确认的优先级作为预期结果。

5.3 画出决策表草稿并手工化简:一点点推,效率反而最高

画这个例子的决策表时,我先按未经约束的全组合来枚举。C1、C2、C3、C4四个原因,理论上有16条规则。但结合需求来看,C1不成立的时候,C2根本不会被校验,E1是唯一结果,C3和C4的取值对结果没有任何影响。这就像高中学物理时先做受力分析再列方程,不变量可以直接合并。

所以我把这16条规则压缩成了这样几条代表性规则:

  • 规则1:C1不成立,无论C2、C3、C4是什么,结果都是E1。这里可以拆成两个小用例:手机号位数错误、手机号不以1开头。
  • 规则2:C1成立、C2不成立,无论C3、C4是什么,结果都是E2。密码长度错误、缺少字母、缺少数字各算一个变体。
  • 规则3:C1成立、C2成立、C3成立,此时无论C4如何,根据已确认的优先级,结果都是E3。
  • 规则4:C1成立、C2成立、C3不成立、C4成立,结果是E4。
  • 规则5:C1成立、C2成立、C3不成立、C4不成立,结果是E5。

经过这样一步手工化简,原本让人头疼的十几条规则被压缩成5大方向,而每个方向里再用等价类、边界值等手法补充数据细节。这种“因果图缩小范围、等价类丰富数据、边界值补充临界点”的组合打法,是我实战中非常喜欢用的一套组合拳。

5.4 把测试用例落到表格里:可以直接照着执行的用例清单

有了规则方向,我通常会把用例表格整理成下面这样。这里不追求一张表覆盖一百条用例,而是展示每类规则的核心关注点。

用例编号覆盖规则手机号密码账号状态预期结果
CG_01规则1-号码位数错误1380013800(10位)任意合法值任意提示“手机号格式错误”
CG_02规则1-首字符错误23800138000任意合法值任意提示“手机号格式错误”
CG_03规则2-密码长度不足13800138000Abc123(6位)任意提示“密码格式错误”
CG_04规则2-密码缺少字母13800138000123456789任意提示“密码格式错误”
CG_05规则2-密码缺少数字13800138000Abcdefghij任意提示“密码格式错误”
CG_06规则3-风控风险账号13800138000P@ssw0rd123风控标记提示“账号异常”
CG_07规则4-账号锁定13800138000P@ssw0rd123连续输错5次提示“账号已锁定”
CG_08规则5-全部正常13800138000P@ssw0rd123正常登录成功并跳转首页

看到这张表,你应该能体会到因果图法在设计阶段的价值了。它把每个用例的来源链路都固定住了,以后需求变更,比如把密码规则改成“必须包含特殊字符”,我只需要回到因果图上去改C2这个节点的判定,然后重新推一遍决策表,就能快速知道哪些用例需要调整,哪些不受影响。

6. 我在实际项目中积累的经验总结:关于因果图法更冷门但实用的那些事

最后这部分,不教基础概念了,就聊聊这些年真正用下来的体会。有些经验书上确实不太会写,但对把你从“会画图”推向“会用图”特别有帮助。

第一,因果图法和判定表法不是两个割裂的方法,而是一个流程的两个阶段。面试时经常被问“因果图法和判定表法有什么区别”,很多标准答案会把它们做成并列关系。但我的理解是,因果图负责把需求逻辑可视化,判定表负责把逻辑结构规则化,两者组合起来才是完整的测试设计过程。单纯画因果图却不转判定表,只能算完成了需求分析;单纯用判定表而不画因果图,碰到复杂逻辑时你又很容易在条件桩排列上迷失方向。

第二,因果图法最适合的场景是“条件之间逻辑关系复杂,且部分条件存在约束”的需求。如果测试对象只有两三个完全独立的输入框,每个输入框只需单独校验,那用等价类加边界值就够了,硬上因果图反而显得笨重。可一旦出现像“A且B,或C且非D”这种组合关系,因果图法就是最合适的选择。

第三,面对执行时间紧张的迭代,不要追求把因果图画得完美再动手。我见过不少测试同事对着需求文档憋半天,就为画一张“标准”的因果图。其实因果图的核心价值在于逼你想清楚原因、结果和约束,至于画得好不好看,一点都不重要。我自己的做法是先用草稿纸拆出原因和结果清单,再用逻辑表达式把它们的关系写下来,最后才整理成一张正式的图去参加评审。

第四,因果图法对需求文档的反向推动力非常强。每次画图过程中发现的歧义和矛盾点,我都会单独记录到一个“需求疑问清单”里,在测试用例评审前发给产品和开发。这一点在团队协作里特别加分。因为测试人员最怕的不是用例设计慢,而是测到一半发现开发的理解和需求文档完全不是一回事。

第五,关于自动化和代码生成,现在的确有一些工具能辅助生成决策表和测试用例,但我不建议刚开始学的时候过度依赖工具。手工完整走一遍从需求到因果图、从因果图到决策表、从决策表到测试用例的流程,能在你脑子里建立起一套非常扎实的逻辑分析框架。以后哪怕工具换了流程变了,你的分析能力也不会被替换掉。

最后给准备面试的朋友一个建议:面试官问因果图法时,不要只背定义和符号。最好准备一两个自己实际分析过的案例,讲清楚你是怎么从需求里识别原因和结果,怎么处理中间节点,怎么通过约束简化决策表,以及怎么把决策表最终落地成测试用例的。这种“会做题,也会讲题”的状态,比背一百道八股文都更有说服力。

因果图法说到底,是逼我们用结构化的方式去面对复杂逻辑。软件测试这个行当越往后做越会发现,发现bug的速度和技术工具关系不大,真正拉开差距的,恰恰是你能在测试设计阶段多想透多少层。而因果图法,就是让我在“多想透”这件事上少走弯路的那张地图。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询