1. 先对齐语境:你说的“case”,到底是哪一个 case
很多人在聊“一个 case 由哪些属性组成”的时候,讨论到一半就变味了,因为“case”这个词实在太容易踩进不同语境。写代码的人脑子里的第一反应是switch case、CASE WHEN这类语法关键字;做数据分析的人想到的是“样本”“个案”;做产品和交互的人想到的是“用户故事”“场景案例”;而做测试和质量的工程师,脑子里蹦出来的基本就是test case,也就是测试用例。
这个区分不是咬文嚼字。你不把语境对齐,后面聊“属性分层、正交、组合”全是空中楼阁。我在实际工作中发现,最常把“case 的属性”当成一个正经问题来研究的,恰恰是测试领域——因为一条测试用例身上挂的信息太多了,ID、名称、优先级、前置条件、数据、步骤、预期结果、状态、关联需求、版本、执行人……随随便便就十几二十个字段。这些字段谁该留、谁该拆、谁和谁不能互相依赖、谁和谁必须组合覆盖,直接决定你的用例库是资产还是包袱。
所以这篇文章以“测试用例(test case)”作为主视角来拆解。但方法论本身是通用的:你做数据样本设计、做用户案例归档、做接口场景梳理,都可以套用同一套分层和正交思路。适合人群很明确:刚入行还在手写用例的测试同学,正在搭建测试平台或用例管理体系的工程师,以及被“用例数量爆炸”困扰的团队负责人。
2. 属性为什么要分层:把一坨字段变成有结构的体系
2.1 不分层的用例库是什么样子
我见过太多团队的用例库,最典型的形态就是一张巨大的 Excel 或者一个塞满字段的在线表格:第一列 ID,第二列名称,第三列优先级,第四列模块,第五列前置条件,第六列步骤,第七列预期结果,然后还有第八列第九列……看起来每条记录都“信息完整”,但真正用起来处处别扭。
举个真实例子。某个项目要对登录模块做一次接口字段升级,原本的username字段改成account。理论上这只影响“测试数据”相关的用例,但因为大家写用例的时候习惯把字段名、预期结果、前置条件混在一起,导致整个模块一百多条用例全部要翻一遍,光改数据就改了三天。这就是不分层的代价:字段之间彼此耦合,一个点变了,处处要跟着动。
这个问题的本质,和嵌入式系统分层软件架构要解决的问题一模一样。你做嵌入式开发的时候,不会把驱动层、操作系统层、应用层全揉在一个大文件里,因为那样你没法单独升级某一层。用例的属性设计也是一样,它就是一个微型的“软件架构”,分层的目的是让每一层可以独立变化、独立维护。
2.2 我常用的五层属性模型
经过几年反复调整,我现在把一条测试用例的属性拆成五个层次。这个模型不一定适合所有团队,但骨架可以参考,因为它的划分依据是“这些属性到底在为谁服务”。
第一层,标识与溯源层。这是用来“认出这条 case”的属性——用例 ID、名称、创建人、创建时间、关联需求编号、所属版本。它们的核心特征是:一经创建基本不变,用来做追踪和索引。
第二层,逻辑与场景层。这是用来“理解这条 case 在验证什么”的属性——所属模块、功能点、业务场景、用例类型(功能/接口/性能/安全)。这一层回答的问题是:这条 case 的业务价值是什么,它对应哪条需求。
第三层,数据与输入层。这是用来“喂给系统”的属性——输入参数、测试数据、账号数据、环境配置。这一层是变更最频繁的,因为测试数据随着环境、版本、接口变动一直在变。
第四层,执行与控制层。这是用来“让这条 case 跑起来”的属性——前置条件、操作步骤、执行方式(手工/自动化)、优先级、阻塞标记。这一层回答的问题是:这条 case 怎么被执行,能不能进自动化流水线。
第五层,结果与度量层。这是用来“记录这条 case 跑完怎么样”的属性——预期结果、实际结果、当前状态、缺陷关联、执行耗时、覆盖率贡献。
这五层每层服务的对象完全不同:标识层给管理系统看,逻辑层给人看,数据层给测试数据脚本看,执行层给测试引擎看,结果层给报表看。层与层之间尽量低耦合,这就是后面讲“正交”的物理基础。
2.3 分层之后直接带来的三个收益
第一个收益是批量维护变得安全。接口字段升级只动数据层,业务流程调整只动逻辑层,执行方式从手工改成自动化只动执行层。你不需要再像以前一样翻遍整个用例库去猜哪个字段该改。
第二个收益是跨项目复用成为可能。我最近在做的一个产品线,三个子系统的核心登录链路其实是一样的,差异只在测试数据那块。我把逻辑层和执行层的用例模板抽出来共享,每个项目只维护自己的数据层,新项目接入测试体系的时间从两三天压缩到半天。
第三个收益是自动化和手工用例可以统一管理。以前很多团队把手工用例和自动化用例分开建库,结果两边数据对不上。分层之后,自动化脚本挂在执行层,手工执行也挂在执行层,同一份逻辑层和预期结果层,只是执行方式不同而已。这样统计自动化覆盖率的时候,口径自然就统一了。
我在带团队的时候一直强调:case 不是写出来给人看的,它是“给人看 + 给机器跑 + 给报表统计”三套体系共同消费的数据。一坨字段做不到这些,只有分层的数据结构才能做到。这也是为什么很多主流用例管理平台会把“前置条件、步骤、预期结果、附加数据”分开字段存放,而不是混在一个文本框里。
3. 属性的正交:让每个属性只干一件事,且只被改一次
3.1 从施密特正交化说起,一个数学直觉
聊“属性正交”之前,先安一个数学直觉。线性代数里有个施密特正交化公式,做的事情很简单:给一组向量,如果它们之间有重叠、有斜交,就通过一系列投影和减法,把它们改造成一组两两垂直、互不相关的向量。垂直意味着什么?意味着任何一个向量的长度变化,不会影响其他向量的方向。
属性设计里的“正交”就是这个感觉:两个属性之间尽量不重叠、不互相决定。一个属性被修改的时候,其他属性不应该被连带修改。这跟施密特正交化在精神上是一致的——“把有重叠的信息空间,改造为相互独立的信息轴”。
举一个反面例子。我看过某条用例的预期结果字段是这样写的:
页面显示“操作成功”,同时将用户登录状态写入 session,并跳转到首页,数据库中的 last_login 字段被更新。
这个字段看起来信息很全,但仔细一看,它至少包含了三个不同维度的信息:UI 层的页面反馈(显示成功)、会话层的状态(写 session)、数据层的落库结果(last_login 更新)。如果某天登录逻辑改成不再更新 last_login,这条用例你都不知道该改哪里——是改预期结果,还是改前置条件,还是另开一条新用例?
正确的做法是把这个信息拆开:预期结果字段只写“页面显示操作成功,并跳转到首页”;会话状态是执行控制层的属性,单独写“执行后 session 中包含用户标识”;数据库落库是独立的接口/数据校验点,放到数据与输入层的校验规则里。这样三个信息各归各位,改一个不影响另外两个,这就是正交。
3.2 四个问题快速判断属性是否正交
我在评审用例的时候,不太喜欢一条一条去读字段内容,太耗时。我总结了一套快速检查方法,四个问题问过去,基本就能筛出不正交的字段。
第一个问题:这个信息在别的属性里是不是已经存在了?如果“输入账号”和“前置条件:用户已注册”里都写了同一个测试账号,这就是重复。数据应该只存在一个地方,另一个地方用引用而不是复制。
第二个问题:改了 A 属性的值,B 属性是不是必须跟着改?比如“接口地址”和“预期结果里的返回码”,如果接口地址换了,返回码必然要换,说明这两个属性耦合了。要么把返回码从预期结果里拆出去,交给通用的接口校验层,要么把它们合并为一个“接口契约”属性。
第三个问题:这个属性里是不是包含两个以上互不相关的信息?像刚才那个例子,“写 session”和“更新 last_login”就是互不相关的,硬塞在一条预期结果里就是混叠。
第四个问题:删掉这个属性,其他属性还能不能完整描述这条 case?如果一个属性删掉之后别的字段几乎不受影响,说明它本身可能就是一个独立维度,值得保留;反过来,如果删掉它导致一堆字段没法理解,说明它和别的字段职责重叠了,要处理的是重叠,而不是硬留一个“挂件属性”。
这四个问题的本质是检查“信息唯一性”和“修改连锁性”。正交性好的属性集合,你改任何一个属性的值,影响范围都局限在自己身上。
3.3 正交性不是“绝对独立”,别钻牛角尖
有一个误区必须点出来:属性正交不等于属性取值之间完全不相关。业务上天然关联的东西,你不能因为追求正交就去强行拆开。
举个例子,“用户等级”和“用户积分”这两个属性在业务上强相关——积分涨,等级就涨。如果你非要把它们拆成两个完全独立的维度去设计用例,那组合出来的 case 全是假组合:“高等级用户但积分极低”这种组合在真实系统里根本不会出现,测了也白测。
我理解的正交,是“职责上不重叠”,不是“取值上不相关”。用户等级和用户积分,在“描述用户资产”这个职责上确实重叠了,那就选一个作为主维度,另一个作为辅助约束。反过来,“用户等级”和“登录入口(App/网页/小程序)”这两个属性在职责上完全不重叠,它们才是真正可以正交组合的维度。
那属性取值之间的真实依赖关系怎么处理?有一个技巧:把“依赖关系”本身作为一条用例规则来管理。比如你定义一个规则“高等级用户只在拥有高级会员标签时才走特权登录入口”,那这个规则就是一个独立的业务场景,而不是把用户等级和登录入口两个属性绑死在组合表里。这样既保持组合的覆盖面,又不丢业务真实性。
4. 属性的组合:从组合爆炸到 pairwise 与正交表
4.1 组合的数学模型,先算一笔账
属性正交了,下一步就是把不同属性的取值组合起来,生成真正的 case。这里的数学背景是组合数学里的乘法原理:如果有 m 个属性,每个属性有 n 个取值,那全组合数就是 n 的 m 次方。
我举一个很现实的例子。假设你要测一个查询接口,有四个属性维度:排序方式(3 种)、筛选条件(3 种)、分页大小(3 种)、用户类型(2 种),这个规模看起来不大吧?但全组合就是 3 × 3 × 3 × 2 = 54 条 case。如果再加一个“返回格式”属性,3 个取值,就变成 162 条。现实项目里一个模块随随便便就是七八个维度,全组合直接上千条,这个量级手工根本执行不过来,也维护不起。
所以组合策略的核心目标就一句话:在可控的用例数量里,尽量多地覆盖属性之间的相互作用。常见的策略有这么几档——全组合(覆盖率 100%,数量不可控);单因素覆盖(每个属性的每个取值至少被覆盖一次,数量锐减但交互覆盖差);pairwise,也就是两两组合覆盖(任意两个属性的任意一对取值至少出现在一条用例里);以及正交表(更均匀地覆盖,通常配合统计模型用)。
大部分团队实际采用的是“pairwise + 人工补核心场景”的组合方式。原因很朴素:大量的测试实践和缺陷统计数据表明,绝大多数缺陷是由两个因素的交互触发的,三个及以上因素真正同时交互才会触发的缺陷占比很小。所以 pairwise 用大约全组合五分之一到十分之一的数量,就能覆盖到绝大部分交互风险,性价比非常高。
4.2 手工构造 pairwise 组合,其实不复杂
pairwise 的原理听起来玄乎,手工会拆一次就明白了。拿三个属性举例:A 有 3 个取值(A1、A2、A3),B 有 3 个取值(B1、B2、B3),C 有 3 个取值(C1、C2、C3),全组合 27 条。
pairwise 的思路是:保证每一对取值组合至少出现一次。比如 A 和 B 之间有 3×3=9 种组合,A 和 C 之间有 3×3=9 种,B 和 C 之间有 3×3=9 种,合计 27 对。我们的目标是尽量用最少的 case 去覆盖这 27 对组合。
手工构造可以用一个简单的贪心方法。先取 A1、B1、C1 作为第一条。然后尽量让每下一条 case 覆盖更多还没覆盖到的“对”。我快速排一个 9 条的方案出来:
| case | A | B | C |
|---|---|---|---|
| 1 | A1 | B1 | C1 |
| 2 | A1 | B2 | C2 |
| 3 | A1 | B3 | C3 |
| 4 | A2 | B1 | C2 |
| 5 | A2 | B2 | C3 |
| 6 | A2 | B3 | C1 |
| 7 | A3 | B1 | C3 |
| 8 | A3 | B2 | C1 |
| 9 | A3 | B3 | C2 |
检查一下 A-B 对:A1B1、A1B2、A1B3、A2B1、A2B2、A2B3、A3B1、A3B2、A3B3,全部覆盖。A-C 对:A1C1、A1C2、A1C3、A2C1、A2C2、A2C3、A3C1、A3C2、A3C3,全部覆盖。B-C 对:B1C1、B1C2、B1C3、B2C1、B2C2、B2C3、B3C1、B3C2、B3C3,也全部覆盖。9 条 case 覆盖 27 个两两组合,效率比 27 条全组合高了一大截。
实际项目里我一般不用手工排,直接用现成的 pairwise 工具生成。PICT、AllPairs 这些我都用过,输入输出思路基本一样:声明每个属性有哪些取值,工具自动吐一个最小用例集。但我必须警告一句:工具只负责“数学上覆盖均匀”,不负责“业务上有意义”。它给你吐出的组合里,一定会有一些业务上不可能出现的组合(比如“已注销用户 + 使用有效验证码成功登录”),这些组合需要人工筛掉或改写,不能拿到手就用。
4.3 用正交表的时候,组合只是生成的半成品
有人会把“正交表”和“case”画等号,这是另一个大坑。正交表本身只是一个二维矩阵,它规定的是“哪些取值组合要被执行”,但它没有告诉你这个组合要输入什么具体数据、要按什么步骤执行、断言什么结果。从正交表到真正的可执行 case,中间还差一大截。
我之前带过一个新同学,直接用 pairwise 工具生成了一张 20 行的组合表,然后把这 20 行“组合”直接写进用例平台,每一行的步骤、预期结果都是空的,美其名曰“先铺数据再补内容”。结果根本没法执行,因为组合表里只有“用户类型=新用户、支付方式=微信、渠道=小程序”这种维度标记,既没有具体的账号数据,也没有断言的预期页面状态。
我的习惯是:正交组合表只是用例生成器的输出,它必须再经历一次“加工”,补上三层东西。第一层是数据实例化——把抽象取值(新用户、有效验证码)替换成真实可用的测试账号和验证码策略。第二层是执行步骤化——把“组合命中的场景”翻译成具体操作步骤。第三层是断言具体化——把“登录成功”“显示错误提示”这类抽象结果写成可观测、可验证的具体预期。完成这三步,一条 case 才算真正可用。
4.4 组合策略里,边界和异常永远要人工补
pairwise 和正交表解决的是“多个属性取值之间的交互覆盖”,但它们对“单个取值的边界和异常”覆盖得并不好。空值、超长字符串、特殊字符、恰好等于阈值的边界值、超过阈值的越界值,这些典型缺陷触发点不在正交组合的射程范围内。
还是用登录场景举例子。pairwise 组合可能覆盖到“密码错误”这个取值,但不一定会覆盖“密码为空字符串”和“密码 20 位超长”这两个极端点。实际上很多系统在空值和超长输入上处理的逻辑是完全独立的代码分支,这两个点不测,风险一直都存在。
所以我在设计用例集的时候,用的组合策略从来都是三层结构:第一层,用 pairwise 生成核心交互覆盖主集;第二层,针对每个输入属性手工补边界值、空值、超长值、非法字符;第三层,梳理业务规则分支,把组合表没有覆盖到的关键业务场景(比如账号被锁定的分支、验证码过期的分支)单独补 case。这三层合起来,才算是一个有底气的用例集。
5. 实操过程:把“用户登录”完整走一遍分层到组合
5.1 场景定义与属性抽取
空谈理论没有说服力,我拿一个真实的登录模块把流程走一遍,大家可以直接照着这个思路套到自己的项目里。
先定场景:我们要测“用户通过密码方式登录”。先别急着写 case,第一步是抽取这个场景的测试属性维度。我梳理之后,锁定四个维度:账号状态、密码策略、验证码机制、登录入口。
每个维度的取值这样定。账号状态取三个值:正常有效账号、已锁定账号、已注销账号。密码策略取三个值:密码正确、密码错误、密码为空。验证码机制取两个值:验证码正确、验证码错误。登录入口取两个值:App 登录、网页登录。
四个维度,取值数量是 3×3×2×2,全组合 36 条。这个数量手工执行还能接受,但为了演示 pairwise 的效果,我还是用组合工具生成一个更精简的集子。生成的 pairwise 结果大概是 9 到 12 条,核心覆盖了“任意两个维度的一对取值至少同时出现一次”。
这里有个取舍要说明白:36 条全组合当然覆盖最全,但执行成本太高。pairwise 的 12 条覆盖了两两交互,同时配合后面的人工补集,风险缺口完全可以补上。实际项目里如果这个登录模块改动频繁,我更倾向 12 条主集 + 8 条人工补集,比 36 条全量更容易维护。
5.2 正交组合生成与加工
pairwise 工具生成的结果是一张“维度取值矩阵”,不能直接用。我拿其中一条来演示怎么把它加工成真正的 case。
假设工具生成了一行组合:账号状态=正常有效账号、密码策略=密码错误、验证码机制=验证码正确、登录入口=App。这行组合的含义是:验证“在账号正常、验证码正确的情况下,密码错误是否会导致登录失败,并且系统是否给出了正确的错误提示”。
要把这行组合变成可执行的 case,需要补齐具体数据:准备一个状态正常的测试账号(比如normal_user_001),密码字段填一个确定的错误值(比如wrong_password_001),验证码用测试环境万能码888888,入口选 App 端。操作步骤是:打开 App,进入登录页,输入账号、错误密码、正确验证码,点击登录按钮。预期结果是:登录失败,页面提示“用户名或密码错误”,且不跳转首页。
这一步就是把抽象取值“实例化”。我见过很多团队在这一步偷懒,觉得账号随便填一个就行。实际上测试账号的数据准备是最该花时间的,账号的状态(是否已激活、是否被锁、是否绑定了特定权限)直接决定用例能不能真正触发目标分支。我在每个项目里都会维护一张“测试账号状态表”,把账号、密码、状态、所属环境、有效期列出来,case 里只引用账号 ID,不直接复制账号密码,这样账号轮换时只需改表,不用改 case。
5.3 人工补集:边界、异常、业务分支
主集 12 条生成好之后,开始第二层的人工补集。
边界与异常这一块,我至少会补这几条:
| 用例 | 补集类型 | 数据要点 | 预期结果 |
|---|---|---|---|
| 13 | 密码空值 | 密码字段不填,直接点登录 | 按钮置灰,或提示“请输入密码”,不发请求 |
| 14 | 密码超长 | 密码填 128 位随机串 | 系统截断或提示长度超限,不崩溃 |
| 15 | 账号格式非法 | 账号填“not_an_email” | 提示“账号格式不正确” |
| 16 | 验证码过期 | 先获取验证码,等过期后再提交 | 提示“验证码已过期,请重新获取” |
| 17 | 连续失败锁号 | 连续输错 5 次密码 | 账号状态变更为锁定,提示“登录失败次数过多,账号已锁定” |
补集的这五条,全部是用在真实系统里踩过坑踩出来的重点。尤其是 17 号那条“连续失败锁号”,很多团队把它当成普通交互用例,但它在实现层面其实是一个独立的状态机处理逻辑,必须专门验证。
然后是业务分支补集。比如“已锁定账号即使输入正确密码也无法登录”这条,pairwise 的取值组合里可能覆盖到,但预期结果不一定是验证锁定逻辑本身。我会单独加一条 case 来验证锁定分支:账号锁定状态下,输入正确密码和正确验证码,预期结果是提示“账号已锁定,请联系管理员解锁”。这条 case 的重点不是密码验证,而是账号状态机的优先级判断。
5.4 落库与自动化字段的映射
case 设计好了,最后一步是把它落到管理载体里。我的建议是把 case 当做一个数据模型来设计存储结构,而不是当作文档来写。
如果你是用代码管理 case(比如把用例写成 YAML 或 JSON),属性分层的模型非常直观。每个 case 就是一个对象,五层属性对应五组字段。这里其实和 Python 类属性的设计思路很接近:类属性定义的是所有实例共享的结构,而每个 test case 实例的属性值就是具体的测试数据。你用 Python 写测试框架的时候,定义一个LoginCase的 dataclass,把标识层、逻辑层、数据层、执行层、结果层的字段分别声明好,然后实例化时只填值。这样后续做数据驱动测试时,只需要批量替换数据层的字段值,case 逻辑完全复用。
存储上我建议参考 HDF5 格式的设计哲学:元数据(属性)和二进制数据(数据集)分离存放。case 的描述信息、步骤说明、优先级这类元数据,和它的测试输入数据、预期输出数据,分开管理。元数据变更不触发测试数据变更,测试数据变更也不影响元数据。我在团队内部实现的时候,用一个主表存 case 元数据,一个子表存测试数据和环境变量,两个表通过 case ID 关联。这和 HDF5 的属性/数据集分离、Nacos 的配置分层管理是同一个道理——数据按变更频率和用途分开放,互不污染。
我自己的落地习惯是这样的:五层属性里,标识层和逻辑层放进用例管理平台;数据层放进单独的数据配置文件(按环境区分,dev/test/prod 各一份);执行层里的自动化标记挂在 CI 流水线的调度配置里;结果层完全让测试报告系统从执行结果中自动采集,不人工维护。这套结构跑了一年多,最大的感受就是:改 case 的时候胆子大了,不怕改一处崩一片。
6. 常见问题与排查技巧实录
6.1 问题速查表
把这几年的经验沉淀成一张问题速查表,大家对照自查。
| 问题 | 典型原因 | 解决思路 |
|---|---|---|
| case 数量爆炸,跑不完 | 盲目用全组合,没有做 pairwise 或正交设计 | 先按维度拆属性,再用 pairwise 生成主集 |
| 改一个接口字段,几十条用例都要动 | 数据依赖逻辑,字段值写死在预期结果里 | 把测试数据抽到数据层,预期结果引用数据变量 |
| 用例名称重复,无法定位 | 逻辑层没有定义功能点和场景 | 制定命名规范:模块_功能点_场景_编号 |
| 优先级天天变,统计口径混乱 | 优先级属性没有固定枚举 | 定义 P0-P3 的判定标准,写进团队规范 |
| 自动化脚本和手工 case 内容不一致 | 两套载体没有统一字段模型 | 共用一个数据源,脚本和手工只是执行方式不同 |
| 测试数据失效,case 总是跑挂 | 数据层没有专人维护生命周期 | 建测试账号状态表,定期巡检,case 引用 ID 不引用明文 |
| 接口返回和 UI 断言混在一起 | 预期结果字段职责不清 | 预期结果只关注当前层面的可观测输出,其他层面建独立断言 |
| case 评审费时费眼,看不出问题 | 没有一套正交接查清单 | 用 3.2 节的四个问题逐条过滤 |
6.2 属性不是越细越好,先做最小集
跟新手强调“属性要分层、要正交”,很容易走火入魔,把 case 的字段越加越多,最后一条 case 挂二十几个字段,录入成本巨大,维护成本更是灾难。
我的建议是:先做最小属性集,再按实际需要扩展。一个 case 真正跑起来只需要六个字段——ID、名称、前置条件、操作步骤、预期结果、测试数据。其余的字段是为了满足管理和统计需求才逐步加上的。你如果发现某个属性加进去之后没有人填写、没有报表消费、没有自动化使用,那它就是冗余属性,果断删掉。
判断一个属性字段是“必需”还是“虚荣”的方法很直接:你问团队里的三个角色——测试执行的人、测试设计的人、测试管理的负责人——这个属性他们每个人用不用。三个人里只要有一个说“用”,就保留;两个以上说“基本不碰”,就考虑合并或删除。
这里有个真实反差案例。我们团队以前用例库里有一个“用例复杂度”字段,分了简单/中等/复杂三档,初衷是想统计测试工作量。结果半年下来,绝大多数人录的时候都选“简单”,因为没人愿意花时间去认真评估复杂度,这个字段彻底沦为无意义数据。后来我把它删了,换成“预估执行时长(分钟)”,因为这是执行排期真实需要的输入,大家录数就有动力了。
6.3 组合工具不是银弹,它只解决分布问题
最后必须泼一盆冷水:pairwise 和正交表解决了“组合爆炸”的数量问题,但根本不解决“业务正确性”问题。工具不知道你的系统里哪个组合是真的有意义的,也不知道哪个分支逻辑是核心,更不知道哪个输入是高风险边界。
我踩过的最深的一个坑,是早期接手一个支付模块,完全依赖 pairwise 生成用例集,结果上线后线上还是出了一个严重问题——一个“余额充足但支付密码连续输错三次导致账号冻结”的场景漏测了。为什么漏了?“连续输错三次”不是属性取值组合的问题,而是一个账户状态翻转的状态机逻辑,pairwise 里根本没有这个“状态过程”维度,它只对“静态属性取值”做组合。
后来我把组合设计方法论升级了:属性不光是“账号状态”“支付方式”这种静态维度,还要把“操作序列”“状态迁移”“时间先后”这种过程维度抽象成属性。比如“密码输错次数(0/1/2/3)”就是一个过程维度属性,它的取值代表一种累积过程。这种属性引入之后,pairwise 才可能帮你覆盖到状态机上的关键分支。
当然,即便是这样,组合工具也代替不了业务分析。我的底线原则是:正交组合主集负责“面”的覆盖,人工补集负责“点”的深度,业务评审负责“逻辑”的正确。三者缺一不可。
最后再分享一个我个人工作中的小习惯。每次新项目启动测试设计之前,我做的第一件事不是写 case,而是先拉着开发、产品一起开一个半小时的“属性定义会”,把模块的所有测试维度拉成一张表,注明每个维度的取值范围和业务约束,评审通过后再生成具体用例。这一步看起来是在“浪费时间”,但实际上把后面用例评审、执行、维护的整体时间至少压缩了一半。case 的属性设计这件事,越早对齐,后面越省事。