3. 数值型输入边界测试:整数、浮点数、负数、零、最大值最小值

数值边界看起来简单,实际踩坑率特别高。我见过太多线上事故:明明传了个“没问题”的值,结果程序就挂了。说白了,边界测试就是专门找那些“有可能出事”的数值。整数、浮点数、负数、零、最大值、最小值,每一个都能挖出窟窿。今天我把这些年碰到的数值边界问题梳理一遍,希望对你有帮助。

3.1 为什么要单独拎出数值边界?

绝大多数业务逻辑都依赖于数值计算。你写个输入框让用户填年龄,你以为范围是0-150,结果用户传了个-1或者9999999,程序就崩了。我自己的项目里,超过一半的线上缺陷来自数值边界的漏测。

数值边界测试的核心是这四类:

  • 整数边界:负数、零、正阈值、溢出
  • 浮点数边界:精度、特殊值(NaN、Inf)、舍入误差
  • 零值:除零、累加起点、空集合判断
  • 最大/最小值:语言与类型上限、数据库字段限制

下面这张图可以帮你快速建立整体认知:

数值边界测试核心流程 输入数值参数 数值类型 整数边界 浮点数边界 负数 / 零 最大 / 最小值 精度 / 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) 潜在影响
NaNfloat('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 显示精度做了映射测试。
💡 最后一个小建议: 写测试之前,先列一张“边界值清单”表格,把整数、浮点、零、特殊值都写上。然后对着清单一个一个测,别跳着测。这样效率最高,也最不容易漏。