逻辑盲区(中):循环边界条件、状态机跳转遗漏、异常路径未覆盖

好,咱们接着聊逻辑盲区。上一章我们讲了条件判断和分支覆盖的问题,这一章我重点说说循环、状态机和异常路径。这三个东西,说白了是测试中最容易「漏掉」的地方。我自己踩过的坑,十个有八个跟它们有关。

一、循环边界条件:你以为跑通了,其实没跑对

循环代码,看起来简单。不就是 for、while 嘛。但你真的测全了吗?

我举个例子。你写了个循环,处理一个数组:

for (int i = 0; i < array.length; i++) {
    process(array[i]);
}

嗯,看起来没问题。但测试的时候,你只测了数组有 3 个元素的情况。那如果数组是空的呢?如果数组只有一个元素呢?如果数组有 10000 个元素呢?

我个人习惯,测循环边界条件时,至少覆盖这几种情况:

  • 零次循环:循环条件一开始就不满足,循环体一次都不执行
  • 一次循环:刚好满足条件,只执行一次
  • 多次循环:正常执行若干次
  • 最大次数循环:接近或达到上限
  • 越界情况:循环变量超出预期范围

核心要点:循环的边界,往往就是 bug 的藏身之处。尤其是「空循环」和「单次循环」,最容易被人忽略。

我在项目中遇到过一个问题。一个数据处理模块,循环读取文件行。测试时文件有 100 行,一切正常。上线后,用户传了个空文件,程序直接崩溃了。为什么?因为循环体里假设至少有一行数据,空文件导致数组越界。你说冤不冤?

二、状态机跳转遗漏:状态多了,路径就乱了

状态机,说白了就是程序在不同状态之间切换。比如一个订单系统,状态可能是:待支付、已支付、已发货、已签收、已取消。每个状态之间都有跳转规则。

测试状态机,最怕什么?最怕漏掉某个跳转路径。

我画了一张图,帮你理清思路:

待支付 已支付 已发货 已签收 已取消 支付 取消 发货 取消(退款) 签收 退货 订单状态机跳转图 图例: 正常跳转 异常/取消跳转 正常状态 终止状态

你看这张图,每个箭头代表一个跳转路径。测试的时候,你得确保每个箭头都走到了。但现实中,很多人只测了「正常路径」——比如待支付→已支付→已发货→已签收。那些「异常路径」呢?比如已支付直接取消、已发货退货,这些往往被漏掉。

警告:状态机测试,最忌讳「想当然」。你以为某个跳转不可能发生,但用户的操作永远超出你的想象。我曾经遇到一个系统,用户在下单后 1 秒内连续点击「取消」和「支付」,结果状态变成了「已取消已支付」——一个从未定义的状态。系统直接挂了。

我建议你测试状态机时,列一张完整的跳转矩阵表:

当前状态 触发事件 目标状态 是否测试
待支付 支付成功 已支付 ✅
待支付 取消订单 已取消 ✅
已支付 发货 已发货 ✅
已支付 取消(退款) 已取消 ❌ 漏测
已发货 退货 已取消 ❌ 漏测

你看,漏掉的就是那些「非主流」路径。说白了,测试要覆盖所有可能的跳转,而不是只走「阳光大道」。

三、异常路径未覆盖:正常情况谁都会测,异常才是考验

异常路径,指的是程序在非正常情况下的行为。比如网络超时、文件不存在、权限不足、数据格式错误等等。

我见过太多测试用例,只测「正常流程」。用户输入正确数据,系统返回正确结果。但真实世界里,用户会输入各种奇奇怪怪的东西。

举个例子。一个登录功能:

def login(username, password):
    user = db.query("SELECT * FROM users WHERE username = ?", username)
    if user and user.password == hash(password):
        return "登录成功"
    else:
        return "用户名或密码错误"

测试的时候,你测了正确的用户名和密码,也测了错误的密码。但有没有测过这些情况?

  • 用户名包含特殊字符(比如 SQL 注入尝试)
  • 密码为空字符串
  • 用户名超长(比如 1000 个字符)
  • 数据库连接超时
  • 并发登录(同一账号同时多次登录)

小技巧:我个人的做法是,每写一个功能,先问自己三个问题:

  1. 如果输入为空,会怎样?
  2. 如果输入非法,会怎样?
  3. 如果外部依赖挂了,会怎样?

这三个问题问完,异常路径基本就覆盖得差不多了。

我曾经接手过一个项目,用户上传文件的功能。测试时传的都是正常的 txt、jpg 文件。上线后,有人传了一个 2GB 的视频文件,程序直接内存溢出。还有人传了一个带病毒的文件名(比如 ../../../etc/passwd),差点把服务器搞崩了。

嗯,这里要注意。异常路径的测试,不是「锦上添花」,而是「雪中送炭」。正常路径跑通了,只能说明程序在理想环境下能用。但真实环境从来都不理想。

四、如何系统性地覆盖这些盲区?

说了这么多,你可能会问:那我到底该怎么测,才能不漏掉这些盲区?

我总结了一个简单的检查清单,你可以照着做:

  1. 循环测试:列出所有循环,检查是否覆盖了零次、一次、多次、最大次数、越界情况
  2. 状态机测试:画出状态跳转图,确保每个箭头都有对应的测试用例
  3. 异常路径测试:对每个功能点,列出所有可能的异常输入和异常环境,逐一测试

一句话总结:测试不是证明程序能跑,而是证明程序在各种情况下都不会挂。循环边界、状态机跳转、异常路径,这三个地方是 bug 的高发区。你花 80% 的精力去测它们,值得。

好了,这一章就到这里。下一章我们聊聊数据流盲区和并发盲区——这两个坑更大,我到时候给你讲讲我当年是怎么被坑的。


可可精品资料库