自动化与脚本
写 Java 单元测试时如何系统覆盖边界条件与极端输入
用 JUnit 5 + AssertJ 为 Java 代码补齐数值、字符串、集合、浮点、日期和数组索引的边界与极端情况测试。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
写 Java 单元测试时如何系统覆盖边界通用
解决思路
- 识别边界:列出数值极限(MIN_VALUE、MAX_VALUE、零)、字符串状态(null、空串、空白)、集合大小(0、1、多个)
- 应用参数化测试:用 @ParameterizedTest 配合 @ValueSource 或 @CsvSource 覆盖多个边界值
- 测试边界两侧:对每个边界覆盖刚好低于、等于、刚好高于的值
- 每添加一类边界后就运行测试,尽早发现问题
- 验证浮点精度:用 AssertJ 的 isCloseTo(expected, within(tolerance)) 做容差断言
- 测试集合状态:显式覆盖空(0)、单元素(1)、多元素(>1)三种场景
- 处理溢出/下溢:用 Math.addExact() 和 Math.subtractExact() 检测算术溢出
- 测试日期时间边界:验证闰年、月份边界、时区切换
- 根据失败迭代:当某个边界测试失败时,分析错误以发现更多未测试的边界,并为新发现的极端情况补充用例
- 整数边界:用 @ValueSource(ints = {Integer.MIN_VALUE, Integer.MIN_VALUE + 1, 0, Integer.MAX_VALUE - 1, Integer.MAX_VALUE}) 覆盖整数极限,并用 assertThatThrownBy 断言 Math.addExact(Integer.MAX_VALUE, 1) 与 Math.subtractExact(Integer.MIN_VALUE, 1) 抛出 ArithmeticException
- 零值边界:测试被除数为 0 返回 0,除数为 0 抛出 ArithmeticException
- 字符串边界:用 @ValueSource(strings = {"", " ", " ", "\t", "\n"}) 验证空白串被拒绝,单独测试 null、单字符和超长字符串(如 "x".repeat(1000000))
- 集合边界:分别测试空列表(isEmpty、first 为 null、count 为 0)、单元素列表(size 为 1、first 与 last 相同)、大列表(10 万元素)以及集合中含 null 的情况
- 浮点边界:断言 0.1 + 0.2 用容差接近 0.3;验证 POSITIVE_INFINITY 大于 MAX_VALUE、NEGATIVE_INFINITY 小于 MIN_VALUE、NaN 不等于 NaN;验证 1.0/0.0 为正无穷、-1.0/0.0 为负无穷、0.0/0.0 为 NaN
- 日期时间边界:验证 LocalDate.MIN 与 MAX 有效且 MIN 在 MAX 之前;验证闰年 2024-02-29 合法,非闰年 2023-02-29 抛出 DateTimeException
- 数组索引边界:测试索引 0、索引 length-1、负索引抛 ArrayIndexOutOfBoundsException、越界索引抛 ArrayIndexOutOfBoundsException、空数组长度为 0 且访问 empty[0] 抛异常
- 最佳实践:显式测试边界而非依赖随机测试;将 null 与空值同合法输入分开测试;用参数化测试覆盖多边界;验证非法边界输入的错误信息;记录每个边界对业务重要的原因;对所有数值运算测试溢出/下溢
- 注意事项:整数溢出用 Math.addExact() 检测;浮点永不用精确相等而用容差断言;NaN != NaN,用 Float.isNaN() 或 Double.isNaN() 判断;大集合注意内存;用 Unicode 字符测试国际化;日期时间考虑时区和夏令时;数组索引始终测试 0、length-1 和越界
适用边界 · 注意事项
- 非 Java 项目或未使用 JUnit 5 / AssertJ 的测试环境
- 只需验证正常路径、不涉及极限值或特殊输入的简单场景
- 对性能敏感、无法承受大规模集合或超长字符串测试开销的场合