一张状态操作矩阵,把 IoT 测试用例的“不漏“变成有依据的事
一张状态操作矩阵,把 IoT 测试用例的"不漏"变成有依据的事
适用读者:在测扫地机、洗地机、商用清洁机器人这类"多端控制 IoT 设备"的测试同学。如果你写了一版又一版用例,还是担心状态相关的漏测,这篇文章给一个可以直接抄走的方法:把状态和操作铺成一张矩阵,先穷举,再做减法。
一、起因
我在测一台商用清洁设备。它有 15 个状态:空闲、准备中、清洁中、暂停中、自清洁中、结束中、低电回充中、对接充电中、充电中、缺水回充中、对接基站中、换水中、建图中,再加上通用的故障态和警告态。
能操作它的地方有四个:机身触摸屏、手机 App、云端后台,还有一个物理急停按钮。可下发的操作有 13 种:暂停/继续、终止、遥控、建图、切图、充电、上传地图、重定位、分配机器、推送地图、急停、关机、上传日志。
我们公司没有专门针对状态转换的测试——用例都是跟着需求走的。一开始我按常规思路写用例:列基本流、异常流、边界值,密密麻麻写了一大篇,感觉挺全了。
然后线上出了一个问题:机器已经暂停了,App 上还显示"清洁中"。翻遍我的用例,没有一条能覆盖它。
问题出在哪,我后来想明白了:基本流、异常流、边界值覆盖的是"一个功能的输入空间",不是"状态空间"。输入组合可以穷举,状态组合靠想是想不全的。

图 1:线上问题——机器已暂停,App 仍显示"清洁中"
这个 bug 逼着我把状态空间整个铺开,于是有了这张矩阵。后来的事实证明这一步是对的:它挖出的 bug 不止这一个——第七节会讲一个跟着需求走永远测不到的例子。
二、为什么那三个经典方法不够
说清楚这一点很重要,因为它们本身没错,只是适用范围不同。
| 方法 | 它管什么 | 它不管什么 |
|---|---|---|
| 等价类 | 一个输入字段的取值范围 | 系统当前处在什么状态 |
| 边界值 | 取值的临界点 | 状态之间的转换 |
| 场景法 | 一条业务链路的走通 | 链路之外的分支交互 |
设备类产品和普通 Web 后端最大的区别就在这:它有一个真实存在、且随时在变的状态机。同样一个"终止"指令,在空闲状态下发出去毫无意义,在清洁中状态下发出去必须立刻停车——这不是输入的差异,是上下文的差异。
所以这类产品需要一套专门处理"上下文"的方法。它其实早就有名字:状态迁移测试法(State Transition Testing),属于经典黑盒测试设计方法之一。本文讲的是我在实际项目里对它的改造——从"单主体状态机"扩展到"多端双层状态"。
三、做法:先列状态,再列操作
两步,都很朴素:
第一步,把状态列全。 状态从哪来:
- 协议文档里的状态码表、心跳字段里的枚举值
- 每个异步任务的中间态(回充、换水、建图这类,中间态最容易被忽略)
- 不属于正常流程的异常态(故障、警告)
这一步最容易漏的是异步任务的中间态。比如"低电回充中"和"充电中"是两个完全不同的状态,机器在做的事不一样,能接受的操作也不一样,但很多人会把它们当成一个。
第二步,把操作列全。 操作从哪来:
- 每个控制端的 UI 上能点的所有按钮
- 云端后台能给这台设备下发的所有指令
- 物理按键(急停这类)
然后画成一张表:行是状态,列是操作,格子填 ✅ / ❌。
完整版是 15 × 13 = 195 个格子。

图 2:状态 × 操作准入矩阵(15 状态 × 13 操作,"上传日志"为待开发功能,矩阵中预留占位)
四、关键一:操作要先分类,每类有"正确形状"
直接看 13 列平铺的矩阵,你只会看到一堆 ✅❌,没有规律。必须先按作用域分类。
我是这么分的:
| 类别 | 举例 | 正确形状 |
|---|---|---|
| 安全类 | 急停 | 必须全 ✅。出现任何 ❌ 都是严重缺陷 |
| 数据同步类 | 上传地图、推送地图、分配机器 | 几乎全 ✅(不占用机器本体资源) |
| 任务控制类 | 暂停/继续、终止 | 只在存在任务的状态下 ✅ |
| 机器动作类 | 充电、建图、切图、遥控、重定位 | 取决于机器物理状态,规律最复杂 |
| 系统类 | 关机、上传日志 | 特例最多,需要逐条确认 |
分类之后,判 bug 就不需要再逐个格子讨论"该不该允许"了,改问一个更有依据的问题:
这一列属于哪一类?它现在的形状符不符合这一类的预期?
举个实际例子:急停那一列,15 个状态全是 ✅。这是对的——安全类操作在任何状态下都必须可用。反过来说,哪天测试发现某一个状态下急停被禁用了,那不用争论,直接按严重缺陷报。
再比如数据同步类:上传地图、推送地图、分配机器,这三列在 15 个状态下也全是 ✅。这符合预期,因为它们不要求机器停下手上正在做的事。
没有分类的话,你会觉得"195 个格子要一个个想",分完类之后,其中 60 个格子(4 列 × 15 行)一次就判完了。
五、关键二:矩阵自带等价类
这是矩阵法最实用的一个副作用:它会自己把冗余暴露出来。
我把 15 行逐行比对,发现:
低电回充中、对接充电中、缺水回充中、对接基站中—— 4 行的 13 个格子一模一样通用故障、通用警告—— 也完全相同
也就是说,光这两组归并,15 行就折成了 11 个等价类。如果继续按"机器正在做什么"去比对,还能再合。

图 3:4 个"回充类"状态格子完全相同,归并为 1 个等价类
这不是巧合。这 4 个状态虽然触发原因不同(电量低 / 主动回充 / 缺清水 / 基站对接),但机器在做的事是同一件:朝充电座走并对接。对"能接受什么操作"这个问题来说,它们是同一个状态。
同理,那 4 列全 ✅ 的操作,也不需要在 15 个状态里各测一遍,只需要验证"它们确实是全绿"这一个断言。
195 个格子,合并等价类、验证全 ✅ 列之后,真正需要逐格设计用例的不到一半;继续归并,还能压到三分之一以下。
我一直觉得测试设计里最值钱的能力是"知道什么不用测"。矩阵法把这件事变得可计算了:行相同就合并,列单调就只验形状。
六、关键三:准入只是第一层,还差两层
到这里画出来的矩阵,只回答了一个问题:这个操作在这个状态下受不受理。
但完整的用例设计需要三张表:
| 层 | 回答的问题 | 对应缺陷类型 |
|---|---|---|
| ① 准入矩阵 | 这个操作在这个状态下受不受理 | 该给的不给 / 不该给的给了 |
| ② 迁移目标 | 受理之后,状态转到哪 | 转错状态、卡在中间态 |
| ③ 显示对齐 | 转过去之后,各端界面显示对不对 | 各端状态不一致 |

图 4:三张表各回答一个问题——准入矩阵、迁移目标、显示对齐
第 ② 层需要注意的: 每个 ✅ 的格子其实应该再标一个目标状态。比如:
暂停中+继续→ 应该回到清洁中
如果实际回到了 准备中,或者卡在 暂停中 不动——准入矩阵上这一格永远是 ✅,永远发现不了。这类 bug 在实际项目里并不少见,尤其是任务被中断后再恢复的场景。
第 ③ 层是设备类产品独有的: 机器只有一个真实状态,但显示它的地方有好几个(机身屏、App、云端后台)。同一个瞬间,三个地方可能显示三种状态。
我遇到的那个 bug 就是这样:
机器真实状态:暂停
App 显示: 清洁中
单看任何一层都查不出来。只有在"真实状态 × 各端显示"这张二维表上,这一格才会露出来。
顺带说一个真实发现,它暴露了准入矩阵本身的一个坑:设备故障状态下,手机 App 切换地图——不支持;同一个状态下,从云端后台切——可以。 同一个状态、同一个操作,答案居然取决于"指令从哪端发出来"。所以严格来说,矩阵的列定义应该不是"操作",而是"某个端发出的某个操作":要么把切图按端拆成两列,要么给矩阵再加一维。这也是"四个控制端"真正开始咬人的地方——多端产品里,不只是显示要对齐,准入本身也可能不一致。
所以我现在的做法是:准入矩阵负责"能不能做",迁移目标负责"做完了变成什么",显示对齐负责"各端说的是不是同一件事"。 三张表叠起来,才算把状态空间覆盖住。
七、副作用:扫一眼就能发现设计矛盾
矩阵画完之后还有个意外收获:异常格自己会跳出来。
下面是从我那张表里截出来的缩小版,4 个状态 × 6 个操作:
| 当前状态 | 暂停/继续 | 终止 | 充电 | 建图 | 急停 | 关机 |
|---|---|---|---|---|---|---|
| 空闲 | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
| 清洁中 | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| 暂停中 | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| 充电中 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
看最后一列「关机」:空闲能关、清洁中能关、暂停中能关,偏偏充电中不能关。
这一步不需要任何测试执行,光看表就能问出问题:这是硬约束,还是标错了? 如果确实有原因(比如充电中关机可能导致无法再唤醒),那它就是一条需要写进需求文档的约束;如果没有原因,那就是缺陷。
同样的方法可以扫出其他可疑格:
- 某个状态除了两列以外全是 ❌ —— 是设计如此,还是这张表没填完?
- 某个异步状态的「建图」是 ✅ —— 一边回充一边建图合理吗?
- 某一列只有 3 个状态是 ✅ —— 剩下 12 个真的都不该允许吗?
讲一个真实挖出来的 bug:机器建图过程中,在 App 上编辑一张非使用中的地图——不支持。编辑的明明不是机器正在建的那张图,凭什么不让编辑?需求文档里从来没写过这条限制,跟着需求走的用例永远不会碰到它。是"状态 × 操作"这个问法把它逼出来的:把「建图中」和「编辑地图」摆进同一格,问题就从"想不想得到"变成了一个可验证的断言。一测,果然是 bug。
这些问题的答案,往往不在代码里,而在没人写下来的需求里。 矩阵法的一个副产品,就是把"需求缺口"变成了一张可追问的清单。
八、适用边界
最后说清楚这套方法什么时候有用、什么时候别用。
适合:
- 设备有明确的状态机,状态数 ≥ 5
- 有多个控制端(机身、App、云端)能对同一个设备下指令
- 存在异步任务(建图、回充、换水这类会经历多个中间态的操作)
- 有安全相关的操作(急停、急停恢复)
不适合:
- 单状态、纯请求响应的服务(用等价类 + 边界值更快)
- 状态机是严格线性、没有分支的(画矩阵不如直接写场景)
- 状态数超过 50 个的(矩阵会大到失去可读性,得先做分层或分组)
一个提醒: 不要把矩阵当成"每个格子都要写一条用例"。它的用途是穷举可能性、然后做减法——合并等价类、验证形状、挑代表性组合。真按 195 个格子写 195 条用例,那是另一种形式的浪费。
如果你也在测带状态机的设备,建议先干一件事:把状态列全,把操作列全,画一张表。不用画得漂亮,Excel 就行。画完你会发现,很多以前想不起来的场景,是被这张表逼出来的。
如果你已经在用类似的方法,或者对"多端双层状态"有更好的落地方式,欢迎在评论区聊聊。需要那张 15×13 空表模板(Excel)的话,留言我看到会整理出来。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)