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 状态迁移测试用例设计

有了状态图,写用例就简单了。我一般遵循“全覆盖”原则:

  1. 覆盖所有状态:每个状态至少进入一次
  2. 覆盖所有合法迁移:每条箭头走一遍
  3. 覆盖所有非法迁移:尝试从A状态直接跳到C状态(代码里可能没做限制)
  4. 覆盖状态循环:比如“已连接→未连接→已连接”反复多次

关键点:状态迁移测试最容易漏掉的是“状态保持”。比如蓝牙已连接时,系统休眠再唤醒,状态是否还保持?这种场景一定要测。

4.3 异常路径覆盖:别让用户“教”你做事

异常路径,说白了就是用户不按套路出牌。我见过太多测试用例只写“正常操作”,结果上线后被用户各种花式操作搞崩溃。

4.3.1 常见的异常路径类型

异常类型 示例 测试要点
操作中断 导航过程中突然熄火 恢复后状态是否正确
操作叠加 连续点击“开始导航”10次 是否重复创建任务
操作冲突 导航播报时唤醒语音 优先级处理是否正确
资源耗尽 存储空间满时下载地图 是否有友好提示
外部干扰 蓝牙连接中突然来电 挂断后是否恢复

4.3.2 异常路径设计方法

我常用的方法是“三步法”:

  1. 找异常点:每个操作步骤,问自己“这里可能出什么问题?”
  2. 造异常条件:模拟网络断开、资源不足、并发操作等
  3. 验证恢复:异常发生后,系统能否自动恢复或提示用户手动恢复

举个例子,导航场景:

正常路径:打开导航 → 输入目的地 → 开始导航 → 到达 → 结束导航
异常路径1:打开导航 → 输入目的地 → 开始导航 → 信号丢失 → 重新规划 → 继续导航
异常路径2:打开导航 → 输入目的地 → 开始导航 → 熄火 → 重新上电 → 恢复导航
异常路径3:打开导航 → 输入目的地 → 开始导航 → 连续点击“退出”5次 → 是否正常退出

个人经验:异常路径不要贪多。我一般每个功能点选3-5个最可能发生的异常场景。太多了用例会膨胀,而且很多异常场景其实用户一辈子也遇不到一次。

4.4 场景法与状态迁移的结合

这两个方法不是孤立的。我习惯把场景法作为“骨架”,状态迁移作为“血肉”。

具体做法是:

  1. 先用场景法画出用户的操作流(基本流+备选流)
  2. 再对每个操作步骤,分析系统的状态变化
  3. 最后在状态变化点上插入异常路径

举个例子,语音助手场景:

用户操作 系统状态变化 异常路径
唤醒语音 空闲→监听中 唤醒失败(麦克风被占用)
说出指令 监听中→识别中 识别超时(网络延迟)
执行指令 识别中→执行中 执行失败(资源不足)
用户打断 执行中→监听中 打断后状态混乱(未正确回退)

曾经踩过的坑:有一次我测语音助手,发现唤醒后快速说两句话,系统直接崩溃了。后来查代码发现,状态机里“监听中→识别中”的迁移没做防重入保护。这就是典型的场景法和状态迁移没结合好——只测了单次操作,没测连续操作的状态变化。

4.5 实战建议

最后给几点实战建议:

  • 先画图,再写用例:不管是场景图还是状态图,画出来能帮你发现很多遗漏的路径
  • 异常路径要“合理”:别为了覆盖而覆盖,想想用户真的会这么操作吗
  • 关注状态恢复:异常发生后,系统能不能回到正常状态,比异常本身更重要
  • 自动化优先:场景法和状态迁移的用例,特别适合用自动化来跑。手动跑一遍太累了

嗯,这一章的内容就这些。场景法和状态迁移,说白了就是让你从“点功能”变成“走流程”。下次写用例的时候,试试先画张图,你会发现很多之前没想到的测试点。