4. 场景法与状态迁移:用户操作流、系统状态机、异常路径覆盖
场景法和状态迁移,这两个东西放在一起讲,是因为它们本质上都在解决同一个问题——用户到底怎么用你的系统,系统又怎么响应。
我刚开始做座舱测试那会儿,总觉得把每个功能点测一遍就完事了。后来被一个bug狠狠教育了一回——用户连续点了三次导航,系统直接卡死了。为什么?因为测试用例只写了“点一次导航”,没写“连续点”。从那以后,我对场景法和状态迁移就特别上心。
核心观点:单点功能测试只能保证“功能能用”,场景法和状态迁移才能保证“功能好用”且“不出错”。
4.1 场景法:从用户视角出发
场景法说白了就是——别把自己当测试工程师,把自己当用户。用户不会按着你的测试用例来操作,他们想怎么点就怎么点。
4.1.1 基本流与备选流
我习惯把场景分成两类:
- 基本流:用户最常用的那条路径。比如“打开导航→输入目的地→开始导航→到达目的地”。
- 备选流:用户可能走的其他路径。比如“导航过程中切换音乐→接听电话→返回导航”。
举个例子,座舱里的“语音助手”功能:
| 场景类型 | 操作路径 | 预期结果 |
|---|---|---|
| 基本流 | 唤醒语音→说出指令→执行指令→退出 | 指令正确执行 |
| 备选流1 | 唤醒语音→说出指令→指令识别失败→重新识别 | 提示用户重说 |
| 备选流2 | 唤醒语音→说出指令→执行中→用户打断→新指令 | 中断当前任务,执行新指令 |
| 备选流3 | 唤醒语音→说出指令→执行中→来电→挂断后恢复 | 来电结束后继续执行 |
你想想看,如果只测基本流,备选流2和3的bug根本发现不了。我在项目中遇到过好几次,语音执行到一半来个电话,挂断后语音直接沉默了——这就是典型的备选流没覆盖到。
4.1.2 场景矩阵设计法
我个人比较喜欢用场景矩阵来组织用例。把用户的操作步骤列成行,把可能的状态或事件列成列,交叉点就是测试点。
| 操作步骤 | 正常状态 | 网络异常 | 资源不足 | 并发操作 |
|---|---|---|---|---|
| 唤醒语音 | 正常唤醒 | 离线模式唤醒 | 内存不足时唤醒 | 音乐播放中唤醒 |
| 说出指令 | 识别成功 | 识别失败转本地 | 识别超时 | 导航播报中识别 |
| 执行指令 | 正常执行 | 执行失败提示 | 执行卡顿 | 多指令冲突 |
小技巧:场景矩阵不要贪多。我一般控制在5×5以内,超过这个规模,用例数量会爆炸。优先覆盖用户高频场景和异常场景。
4.2 状态迁移:系统的“内心戏”
场景法关注的是用户操作,状态迁移关注的是系统内部的状态变化。座舱系统里到处都是状态机——蓝牙连接状态、导航状态、媒体播放状态、空调状态……每个状态都有它自己的“脾气”。
4.2.1 状态机三要素
设计状态迁移用例,先搞清楚三样东西:
- 状态:系统当前处于什么模式(比如蓝牙已连接、蓝牙未连接)
- 事件:什么触发了状态变化(比如用户点击、系统事件)
- 迁移:从一个状态到另一个状态的路径
拿蓝牙连接来举例:
状态:未连接 → 正在连接 → 已连接 → 已断开
事件:用户点击连接 → 连接成功 → 连接失败 → 用户断开 → 信号丢失
迁移:
未连接 + 用户点击连接 → 正在连接
正在连接 + 连接成功 → 已连接
正在连接 + 连接失败 → 未连接
已连接 + 用户断开 → 未连接
已连接 + 信号丢失 → 未连接
注意:状态迁移最容易漏掉的是“非法迁移”。比如从“已连接”直接跳到“正在连接”,这种路径在代码里可能没做保护,测出来就是bug。
4.2.2 状态迁移图
我习惯先画一张状态迁移图,再根据图来写用例。下面这张图展示了蓝牙连接的核心状态流转:
4.2.3 状态迁移测试用例设计
有了状态图,写用例就简单了。我一般遵循“全覆盖”原则:
- 覆盖所有状态:每个状态至少进入一次
- 覆盖所有合法迁移:每条箭头走一遍
- 覆盖所有非法迁移:尝试从A状态直接跳到C状态(代码里可能没做限制)
- 覆盖状态循环:比如“已连接→未连接→已连接”反复多次
关键点:状态迁移测试最容易漏掉的是“状态保持”。比如蓝牙已连接时,系统休眠再唤醒,状态是否还保持?这种场景一定要测。
4.3 异常路径覆盖:别让用户“教”你做事
异常路径,说白了就是用户不按套路出牌。我见过太多测试用例只写“正常操作”,结果上线后被用户各种花式操作搞崩溃。
4.3.1 常见的异常路径类型
| 异常类型 | 示例 | 测试要点 |
|---|---|---|
| 操作中断 | 导航过程中突然熄火 | 恢复后状态是否正确 |
| 操作叠加 | 连续点击“开始导航”10次 | 是否重复创建任务 |
| 操作冲突 | 导航播报时唤醒语音 | 优先级处理是否正确 |
| 资源耗尽 | 存储空间满时下载地图 | 是否有友好提示 |
| 外部干扰 | 蓝牙连接中突然来电 | 挂断后是否恢复 |
4.3.2 异常路径设计方法
我常用的方法是“三步法”:
- 找异常点:每个操作步骤,问自己“这里可能出什么问题?”
- 造异常条件:模拟网络断开、资源不足、并发操作等
- 验证恢复:异常发生后,系统能否自动恢复或提示用户手动恢复
举个例子,导航场景:
正常路径:打开导航 → 输入目的地 → 开始导航 → 到达 → 结束导航
异常路径1:打开导航 → 输入目的地 → 开始导航 → 信号丢失 → 重新规划 → 继续导航
异常路径2:打开导航 → 输入目的地 → 开始导航 → 熄火 → 重新上电 → 恢复导航
异常路径3:打开导航 → 输入目的地 → 开始导航 → 连续点击“退出”5次 → 是否正常退出
个人经验:异常路径不要贪多。我一般每个功能点选3-5个最可能发生的异常场景。太多了用例会膨胀,而且很多异常场景其实用户一辈子也遇不到一次。
4.4 场景法与状态迁移的结合
这两个方法不是孤立的。我习惯把场景法作为“骨架”,状态迁移作为“血肉”。
具体做法是:
- 先用场景法画出用户的操作流(基本流+备选流)
- 再对每个操作步骤,分析系统的状态变化
- 最后在状态变化点上插入异常路径
举个例子,语音助手场景:
| 用户操作 | 系统状态变化 | 异常路径 |
|---|---|---|
| 唤醒语音 | 空闲→监听中 | 唤醒失败(麦克风被占用) |
| 说出指令 | 监听中→识别中 | 识别超时(网络延迟) |
| 执行指令 | 识别中→执行中 | 执行失败(资源不足) |
| 用户打断 | 执行中→监听中 | 打断后状态混乱(未正确回退) |
曾经踩过的坑:有一次我测语音助手,发现唤醒后快速说两句话,系统直接崩溃了。后来查代码发现,状态机里“监听中→识别中”的迁移没做防重入保护。这就是典型的场景法和状态迁移没结合好——只测了单次操作,没测连续操作的状态变化。
4.5 实战建议
最后给几点实战建议:
- 先画图,再写用例:不管是场景图还是状态图,画出来能帮你发现很多遗漏的路径
- 异常路径要“合理”:别为了覆盖而覆盖,想想用户真的会这么操作吗
- 关注状态恢复:异常发生后,系统能不能回到正常状态,比异常本身更重要
- 自动化优先:场景法和状态迁移的用例,特别适合用自动化来跑。手动跑一遍太累了
嗯,这一章的内容就这些。场景法和状态迁移,说白了就是让你从“点功能”变成“走流程”。下次写用例的时候,试试先画张图,你会发现很多之前没想到的测试点。