2. 异常与重试基础
在自动化测试中,脚本执行失败几乎是不可避免的。网络抖动、服务短暂不可用、资源竞争、第三方接口超时,都会让原本正确的测试逻辑在某一刻失败。要构建稳定的测试体系,第一步不是急着加 try/except,而是先理解异常是什么、它如何产生、如何传播、何时应该被处理,再理解重试解决的是哪一类问题。异常与重试是两套机制:异常负责“发现并描述问题”,重试负责“对可恢复问题再给一次机会”。
一、异常的定义与本质
异常(Exception)是程序在执行过程中遇到的、打断正常指令流的特殊事件。它通常包含三部分信息:类型(发生了什么)、消息(具体描述)、调用栈(在哪里发生、经过哪些调用路径)。在测试脚本中,异常不仅是错误信号,更是定位问题的关键证据。例如断言失败、元素找不到、连接超时,虽然都表现为“脚本挂了”,但它们的类型和恢复策略完全不同。
二、异常的分类
从测试脚本的恢复视角,异常可以按“是否可恢复”和“来源”两个维度分类。按可恢复性分为:瞬时异常(网络抖动、限流、临时超时,重试可能成功)、持久异常(配置错误、代码缺陷、数据缺失,重试必然再次失败)、致命异常(进程崩溃、内存溢出,需要终止并上报)。按来源分为:环境异常(网络、DNS、端口)、应用异常(服务返回 5xx、业务错误码)、脚本异常(选择器错误、语法错误、断言不匹配)、数据异常(测试数据被污染、依赖数据未就绪)。
| 异常类型 | 典型示例 | 是否可重试 | 推荐处理方式 |
|---|---|---|---|
| 瞬时环境异常 | ConnectionTimeout、502/503 | 是 | 有限次退避重试 |
| 限流异常 | 429 Too Many Requests | 是 | 按 Retry-After 延迟重试 |
| 脚本异常 | NoSuchElement、语法错误 | 否 | 修复脚本,不重试 |
| 断言失败 | 期望值与实际值不符 | 否 | 记录证据,判定失败 |
| 数据异常 | 依赖数据未生成 | 视情况 | 等待或重建数据后重试 |
| 致命异常 | 进程崩溃、OOM | 否 | 终止并告警 |
三、异常的生命周期
异常从产生到消亡,通常经历五个阶段:抛出(raise)→ 传播(propagate)→ 捕获(catch)→ 处理(handle)→ 终结(finalize)。抛出时,运行时创建异常对象并中断当前执行;传播时,异常沿调用栈向上寻找匹配的处理器;捕获时,最近的 except 或 catch 块接管控制权;处理时,可以记录日志、清理资源、决定重试或重新抛出;终结时,无论是否处理,finally 块都会执行,用于释放连接、关闭文件等。
理解生命周期的意义在于:重试应该发生在“处理”阶段,而不是“捕获”阶段。捕获只是拿到控制权,处理才决定是重试、降级还是失败。很多脚本把重试逻辑直接塞进 except 里,导致异常被吞掉、堆栈丢失,最终无法定位根因。
四、重试的基本概念
重试(Retry)是指当一次操作因可恢复原因失败后,在满足约束条件的前提下再次执行同一操作。一个完整的重试策略通常包含五个要素:重试条件(哪些异常才重试)、最大次数(避免无限循环)、退避策略(固定间隔、指数退避、随机抖动)、超时上限(总耗时不能超过用例预算)、幂等性保证(重复执行不会产生副作用)。
retry_policy:
max_attempts: 3
backoff: exponential
base_delay: 0.5s
max_delay: 5s
jitter: true
retry_on:
- ConnectionTimeout
- HTTP 502
- HTTP 503
- HTTP 429
no_retry_on:
- AssertionError
- NoSuchElementException
- HTTP 400
五、适用场景与常见误区
重试适合处理瞬时性、外部性、可恢复的问题:网络请求超时、服务短暂不可用、限流、消息队列暂时为空、分布式锁竞争。它不适合处理确定性、逻辑性、数据性的问题:断言失败、选择器写错、参数错误、业务规则不满足。把不该重试的异常拿去重试,只会延长失败时间、掩盖真实缺陷。
常见误区二:重试次数过多或间隔过短。在服务已经过载时继续高频重试,会加剧雪崩。
常见误区三:忽略幂等性。对“创建订单”“扣款”等非幂等操作重试,可能产生重复数据。
常见误区四:重试后不记录原始异常。最终只看到最后一次失败,丢失了首次失败的关键现场。
常见误区五:把重试当作修复手段。重试只能缓解瞬时问题,不能替代根因修复。
六、案例:一次接口超时的正确处理
假设测试脚本调用订单查询接口,返回 ConnectionTimeout。正确做法是:捕获该异常后,判断它属于瞬时环境异常,进入重试流程;每次重试前记录尝试次数和耗时;采用指数退避,例如 0.5s、1s、2s;最多 3 次;若仍失败,则抛出带有完整尝试记录的异常,标记用例失败并附上日志。若接口返回的是 400 Bad Request,则说明请求本身有问题,应立即失败,不进入重试。
异常与重试是测试脚本稳定性的两块基石。异常负责准确描述“出了什么问题”,重试负责对“可恢复的问题”进行有限补救。只有先分清异常类型、看清传播路径、守住幂等与上限,重试才不会变成掩盖缺陷的“遮羞布”,而成为提升测试稳定性的有效手段。