3. 数值型输入边界测试:整数、浮点数、负数、零、最大值最小值
数值边界看起来简单,实际踩坑率特别高。我见过太多线上事故:明明传了个“没问题”的值,结果程序就挂了。说白了,边界测试就是专门找那些“有可能出事”的数值。整数、浮点数、负数、零、最大值、最小值,每一个都能挖出窟窿。今天我把这些年碰到的数值边界问题梳理一遍,希望对你有帮助。
3.1 为什么要单独拎出数值边界?
绝大多数业务逻辑都依赖于数值计算。你写个输入框让用户填年龄,你以为范围是0-150,结果用户传了个-1或者9999999,程序就崩了。我自己的项目里,超过一半的线上缺陷来自数值边界的漏测。
数值边界测试的核心是这四类:
- 整数边界:负数、零、正阈值、溢出
- 浮点数边界:精度、特殊值(NaN、Inf)、舍入误差
- 零值:除零、累加起点、空集合判断
- 最大/最小值:语言与类型上限、数据库字段限制
下面这张图可以帮你快速建立整体认知:
3.2 整数边界:从负数到最大值
整数溢出是老生常谈,但依然防不胜防。我自己曾在一个支付系统里犯过错误:用int存储订单金额(单位是分),结果用户连续买了大量商品,总金额超过了32位整数的上限……直接变成负数,系统还给他退款了。嗯,那是个难忘的下午。
整数边界需要测试的典型值:
| 类型 | 测试值 | 常见问题 |
|---|---|---|
| 负数 | -1, -2^31, -(MAX+1) | 处理逻辑未考虑负数,或错误使用了无符号类型 |
| 零 | 0 | 除数为零、空集合判断、与默认值混淆 |
| 正最小值 | 1 | 边界循环、长度校验、数组下标 |
| 正最大值 | MAX (如2^31-1) | 溢出、内存分配失败 |
| 溢出值 | MAX+1, MIN-1 | 回绕、异常中断 |
int a = 2147483647; float b = a; 再转回 int 就变了,因为 float 精度不够。
3.3 浮点数边界:精度与特殊值
浮点数比整数坑更多。我记得有一次同事写了个正则验证金额,用户输入“0.1+0.2”,结果系统一直返回 false,排查半天才发现是浮点精度问题——0.1+0.2 != 0.3。你想想看,这就是 IEEE 754 的锅。
浮点数的边界测试要覆盖以下场景:
- 特殊值:NaN(非数)、Inf(无穷)、-Inf、-0.0
- 精度边界:FLT_MIN、DBL_MIN(最小正规格数)、FLT_MAX、DBL_MAX
- 舍入边界:比如 0.9999999 与 1.0 之间的转换
- 比较操作:不要用
==,应该用abs(a-b) < epsilon
| 测试项 | 示例值(float) | 潜在影响 |
|---|---|---|
| NaN | float('nan') | 所有比较都返回 false,导致死循环 |
| 正无穷 | float('inf') | 溢出计算,结果仍为 inf |
| 负零 | -0.0 | 在某些语言中 -0.0 == 0.0 为 true,但 1/(-0.0) == -inf |
| 最小正规格数 | 1.17549435e-38 | 下溢为0,或性能退化(非规格化数) |
| 最大有限值 | 3.40282347e+38 | 再大就变成 inf |
epsilon 比较,否则结果永远对不上。
3.4 零的特殊地位
零不是“没有值”,而是一个非常特殊的边界。很多bug就出在零值上:
- 除以零 —— 整数除零直接抛异常,浮点除零返回 inf。
- 作为累加起点 —— 如果初始值应该是 null,但用了0,结果可能偏小。
- 与空字符串区分 —— 前端传了个空字符串,后端解析成0,用户实际并没填。
我个人习惯:凡是数值字段,只要有默认值0,就需要单独测“不传”和“传0”两种场景。
3.5 实战:设计一份完整的数值边界测试清单
看完上面的理论,我们来写点实际能用的代码。下面是一个 Python 风格的边界测试框架示例:
import math
def test_numeric_boundary(func, min_val, max_val):
# 整数边界
assert func(-1) == expected
assert func(0) == expected
assert func(1) == expected
assert func(min_val) == expected
assert func(max_val) == expected
assert func(max_val + 1) raises OverflowError
# 浮点边界
assert func(float('nan')) is NaN
assert func(float('inf')) is inf
assert func(-0.0) == func(0.0)
# 精度边界
assert abs(func(0.1 + 0.2) - 0.3) < 1e-9
注意:实际业务中,min_val 和 max_val 要从需求文档里拿。我遇到过好几次需求写了“金额上限 999999”,结果数据库字段是 int(10),实际可以存到 2147483647,然后测试只测了 999999,上线后被人刷单到几十亿……哎,你说是不是要先把“实际边界”搞清楚?
3.6 常见避坑指南
经验之谈: 数值边界测试不是为了追求100%覆盖,而是为了覆盖“最容易出事”的那几个点。用二八定律思考,80%的bug来自20%的边界值。
- 不要假设输入永远合法。 我见过一个API对于负数直接返回错误码,但前端过滤了,结果被第三方接口调用了负数,系统把余额扣成了正数——哦,那个逻辑是
balance = balance - amount,amount 是负数,相当于给用户加钱。 - 注意浮点数的比较。 很多人用
if a == 0.0判断是否为零,但浮点运算后 a 可能是 0.0000000001。应该用abs(a) < epsilon。 - 别忘了“隐式边界”。 比如用户输入一个字符串,内部转成了 int,如果字符串超长,可能抛出
NumberFormatException。这种也要算作数值边界的一部分。 - 我曾经踩过的坑: 一个计费系统用了
Decimal类型,但数据库字段是DECIMAL(10,2),结果传入 99999999.99 时,数据库存不下,自动截断。那订单就少了 0.01 元,用户投诉了一周。后来我老老实实把数据库字段精度和 UI 显示精度做了映射测试。