4. 状态与流程边界:状态机中的边界状态、流程分支的极限情况、并发与竞态条件
状态与流程边界,说白了就是系统在「变」的过程中容易出问题的地方。我做了这么多年测试,发现很多线上故障都跟状态切换有关——不是状态没定义清楚,就是流程走到一半被打断了。
这一节我们重点聊三块:状态机里的边界状态、流程分支的极限情况,还有并发竞态。嗯,每一块都是硬骨头。
4.1 状态机中的边界状态
状态机这东西,看起来简单——几个状态,几条线。但实际跑起来,边界状态往往被忽略。
什么叫边界状态?我举个例子:一个订单系统,状态有「待支付」「已支付」「已发货」「已完成」。看起来挺清晰对吧?但你想过没有——
- 「待支付」超时后应该去哪?
- 「已支付」之后如果退款,状态怎么走?
- 「已发货」能不能直接跳到「已完成」?
这些都是边界状态。我个人习惯,拿到状态机第一件事就是画一张完整的状态转移表,把每个状态的入度、出度、非法转移都列出来。
我记得有一次测试一个工作流引擎,状态机里有个「审核中」状态。文档上说「审核通过后进入已通过」,但没写「审核不通过」怎么办。结果上线后,审核不通过的工单直接卡死在「审核中」——这就是典型的边界状态缺失。
状态转移矩阵
我建议用矩阵来穷举状态边界。下面是一个简化版:
| 当前状态 | 合法事件 | 下一状态 | 边界条件 |
|---|---|---|---|
| 待支付 | 支付成功 | 已支付 | 金额必须等于订单总额 |
| 待支付 | 超时 | 已取消 | 超时时间=30分钟 |
| 已支付 | 发货 | 已发货 | 库存必须充足 |
| 已支付 | 退款 | 已退款 | 退款金额≤支付金额 |
| 已发货 | 确认收货 | 已完成 | 签收后7天内可发起售后 |
你想想看,如果「已支付」状态下同时收到「发货」和「退款」两个事件,系统怎么处理?这就是并发边界了,后面会细说。
4.2 流程分支的极限情况
流程分支,说白了就是if-else。但实际业务中的分支远比代码里复杂。我见过最离谱的一个审批流程,分支条件嵌套了7层,最后连产品经理都说不清楚某个分支什么时候会走到。
分支的极限情况,我总结了三类:
- 空分支:条件集合为空时怎么走?比如筛选条件全部不匹配。
- 全分支:所有条件同时满足时怎么走?优先级怎么定?
- 循环分支:流程走回之前的状态,会不会死循环?
我曾经测试过一个风控系统,它的分支逻辑是这样的:如果用户信用分>600走A分支,否则走B分支。看起来没问题对吧?但测试时我发现,信用分刚好等于600的情况没定义。代码里写的是 >600,等于600落到了else里。但产品文档写的是「≥600走A」。这就是分支边界没对齐。
分支覆盖策略
我一般用这张表来规划分支测试:
| 分支类型 | 测试用例 | 预期结果 |
|---|---|---|
| 单条件分支 | 条件=True, 条件=False, 条件=边界值 | 分别进入对应分支 |
| 多条件组合 | 全True, 全False, 混合组合 | 按优先级或短路规则执行 |
| 循环分支 | 0次, 1次, N次, 最大次数+1 | 正常退出或触发熔断 |
| 异常分支 | 输入为null, 超时, 网络中断 | 进入降级或错误处理 |
嗯,这里要注意:分支的极限情况往往不在正常逻辑里,而在「条件刚好卡在边界上」的时候。
4.3 并发与竞态条件
并发竞态,这是状态与流程边界里最难搞的一块。说白了就是:两个操作同时发生,系统怎么保证数据一致?
我举个例子你就明白了:一个库存系统,商品只剩1件。用户A和用户B同时下单。如果代码没处理好,两个人都能下单成功,但库存变成-1。这就是竞态条件。
我个人习惯,测试并发竞态时用这三板斧:
- 时间窗口分析:找出两个操作之间的「脆弱窗口」,比如读库存和扣库存之间。
- 并发压测:用工具模拟多线程同时操作同一个资源。
- 状态回滚验证:操作失败后,状态是否回滚到之前的状态。
我记得有一次测试一个转账功能。用户A给用户B转100块,同时用户B给用户A转100块。理论上应该互相抵消,但实际测试发现,由于并发问题,两个人的余额都多了100块。这就是典型的「更新丢失」竞态。
常见竞态场景
| 竞态类型 | 场景描述 | 后果 |
|---|---|---|
| 读-改-写 | 先读取数据,修改后写回,中间被其他线程修改 | 更新丢失 |
| 检查-执行 | 先检查条件,再执行操作,条件在中间被改变 | 条件失效 |
| 状态覆盖 | 两个线程同时修改同一状态 | 状态不一致 |
| 超时与重试 | 操作超时后重试,但原操作实际已成功 | 重复执行 |
你想想看,如果「检查-执行」场景里,检查库存>0,然后执行扣减。但检查完到扣减之间,另一个线程已经把库存扣成0了。这时候当前线程再扣,库存就变负数了。
解决方案其实不复杂:加锁、用乐观锁、或者用数据库的事务隔离级别。但问题是,很多开发觉得「概率低就不处理」——嗯,这种想法迟早出事。
4.4 状态与流程的SVG知识图谱
下面这张图把状态、流程分支、并发竞态的关系串起来了。你可以看到,状态机是基础,流程分支是路径,并发竞态是干扰。三者交织在一起,才是完整的边界测试视角。
4.5 实战:一个完整的边界测试案例
最后,我分享一个真实案例。之前测试一个「优惠券领取」功能,流程是这样的:
- 用户点击领取 → 检查库存 > 0
- 检查用户是否已领取(每人限领1张)
- 扣减库存 → 生成用户券记录
看起来没问题对吧?但并发测试时发现了三个边界问题:
- 状态边界:库存=1时,两个用户同时领取,都通过了库存检查,但扣减时库存变负数。
- 分支边界:用户已领取的判断,如果用户刚好在「领取中」状态,系统没处理。
- 竞态条件:检查库存和扣减库存之间没有加锁,导致超发。
嗯,这个案例让我深刻体会到:状态、分支、并发,这三者不是孤立的。它们交织在一起,才是真正的边界。你单独测状态没问题,单独测分支也没问题,但一并发就出事。
所以我的建议是:测试状态与流程边界时,一定要把并发因素加进去。别只测单线程,别只测Happy Path。把并发压测作为状态机测试的标配,你会发现很多隐藏的坑。