尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

测试用例属性设计:从分层模型到pairwise正交组合的完整指南

测试用例属性设计:从分层模型到pairwise正交组合的完整指南 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、A3B 有 3 个取值B1、B2、B3C 有 3 个取值C1、C2、C3全组合 27 条。pairwise 的思路是保证每一对取值组合至少出现一次。比如 A 和 B 之间有 3×39 种组合A 和 C 之间有 3×39 种B 和 C 之间有 3×39 种合计 27 对。我们的目标是尽量用最少的 case 去覆盖这 27 对组合。手工构造可以用一个简单的贪心方法。先取 A1、B1、C1 作为第一条。然后尽量让每下一条 case 覆盖更多还没覆盖到的“对”。我快速排一个 9 条的方案出来caseABC1A1B1C12A1B2C23A1B3C34A2B1C25A2B2C36A2B3C17A3B1C38A3B2C19A3B3C2检查一下 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 的属性设计这件事越早对齐后面越省事。
返回列表