mcpField
U
unit-test-boundary-conditions

unit-test-boundary-conditions

github.com/giuseppe-trisciuoglio测试与质量3654.1k 次安装7 周前更新
代码仓库

关于 unit-test-boundary-conditions

unit-test-boundary-conditions 是面向使用 JUnit 5 的 Java 开发者的测试技能,提供数值、字符串、集合、浮点数、日期时间及索引边界的系统化测试模式。

主要功能

  1. 01测试数值边界与溢出
  2. 02覆盖空值、空字符串和空白字符串
  3. 03检查空集合与非空集合
  4. 04以容差断言浮点数值
  5. 05测试日期、时间和数组边界

适用场景

  1. 01验证数值极限处的行为
  2. 02发现差一错误
  3. 03检查算术溢出与下溢
  4. 04测试闰年和时区转换

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-boundary-conditions

安装后在 Claude Code 中输入/unit-test-boundary-conditions确认已加载。

附带文件references/concurrent-testing.mdreferences/parameterized-patterns.md

详细介绍

单元测试边界条件和边缘情况

概述

使用 JUnit 5 测试边界条件、边界情况和极限值的系统化模式。涵盖数值边界、字符串边缘情况、集合状态、浮点精度、日期/时间极限值以及差一错误场景。

适用场景

  • 数值最小值/最大值限制、空值/空字符串/空白字符输入
  • 溢出/下溢验证、集合边界
  • 差一错误、浮点精度

说明

  1. 识别边界:列出数值限制(MIN_VALUE、MAX_VALUE、零)、字符串状态(空值、空字符串、空白字符)、集合大小(0、1、多个)
  2. 使用参数化测试:使用 @ParameterizedTest 搭配 @ValueSource 或 @CsvSource 测试多个边界值
  3. 测试边界两侧:覆盖每个边界正下方、边界处和正上方的值
  4. 每添加一类边界后运行测试,以便及早发现问题
  5. 验证浮点精度:使用 AssertJ 的 isCloseTo(expected, within(tolerance))
  6. 测试集合状态:明确测试空(0 个)、单个(1 个)和多个(>1 个)元素的场景
  7. 处理溢出/下溢:使用 Math.addExact() 和 Math.subtractExact() 检测算术溢出
  8. 测试日期/时间边缘情况:验证闰年、月份边界和时区转换
  9. 根据失败迭代调整:当边界测试失败时,分析错误以发现其他尚未测试的边界;为新发现的边缘情况添加测试用例

示例

需要:junit-jupiter、junit-jupiter-params、assertj-core。

整数边界测试

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.*;

class IntegerBoundaryTest {

  @ParameterizedTest
  @ValueSource(ints = {Integer.MIN_VALUE, Integer.MIN_VALUE + 1, 0, Integer.MAX_VALUE - 1, Integer.MAX_VALUE})
  void shouldHandleIntegerBoundaries(int value) {
    assertThat(value).isNotNull();
  }

  @Test
  void shouldDetectIntegerOverflow() {
    assertThatThrownBy(() -> Math.addExact(Integer.MAX_VALUE, 1))
      .isInstanceOf(ArithmeticException.class);
  }

  @Test
  void shouldDetectIntegerUnderflow() {
    assertThatThrownBy(() -> Math.subtractExact(Integer.MIN_VALUE, 1))
      .isInstanceOf(ArithmeticException.class);
  }

  @Test
  void shouldHandleZeroEdge() {
    int result = MathUtils.divide(0, 5);
    assertThat(result).isZero();

    assertThatThrownBy(() -> MathUtils.divide(5, 0))
      .isInstanceOf(ArithmeticException.class);
  }
}

字符串边界测试

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class StringBoundaryTest {

  @ParameterizedTest
  @ValueSource(strings = {"", " ", "  ", "\t", "\n"})
  void shouldRejectEmptyAndWhitespace(String input) {
    boolean result = StringUtils.isNotBlank(input);
    assertThat(result).isFalse();
  }

  @Test
  void shouldHandleNullString() {
    String result = StringUtils.trim(null);
    assertThat(result).isNull();
  }

  @Test
  void shouldHandleSingleCharacter() {
    assertThat(StringUtils.capitalize("a")).isEqualTo("A");
    assertThat(StringUtils.trim("x")).isEqualTo("x");
  }

  @Test
  void shouldHandleVeryLongString() {
    String longString = "x".repeat(1000000);

    assertThat(longString.length()).isEqualTo(1000000);
    assertThat(StringUtils.isNotBlank(longString)).isTrue();
  }
}

集合边界测试

class CollectionBoundaryTest {

  @Test
  void shouldHandleEmptyList() {
    List<String> empty = List.of();

    assertThat(empty).isEmpty();
    assertThat(CollectionUtils.first(empty)).isNull();
    assertThat(CollectionUtils.count(empty)).isZero();
  }

  @Test
  void shouldHandleSingleElementList() {
    List<String> single = List.of("only");

    assertThat(single).hasSize(1);
    assertThat(CollectionUtils.first(single)).isEqualTo("only");
    assertThat(CollectionUtils.last(single)).isEqualTo("only");
  }

  @Test
  void shouldHandleLargeList() {
    List<Integer> large = new ArrayList<>();
    for (int i = 0; i < 100000; i++) {
      large.add(i);
    }

    assertThat(large).hasSize(100000);
    assertThat(CollectionUtils.first(large)).isZero();
    assertThat(CollectionUtils.last(large)).isEqualTo(99999);
  }

  @Test
  void shouldHandleNullInCollection() {
    List<String> withNull = new ArrayList<>(List.of("a", null, "c"));

    assertThat(withNull).contains(null);
    assertThat(CollectionUtils.filterNonNull(withNull)).hasSize(2);
  }
}

浮点边界测试

class FloatingPointBoundaryTest {

  @Test
  void shouldHandleFloatingPointPrecision() {
    double result = 0.1 + 0.2;
    assertThat(result).isCloseTo(0.3, within(0.0001));
  }

  @Test
  void shouldHandleSpecialFloatingPointValues() {
    assertThat(Double.POSITIVE_INFINITY).isGreaterThan(Double.MAX_VALUE);
    assertThat(Double.NEGATIVE_INFINITY).isLessThan(Double.MIN_VALUE);
    assertThat(Double.NaN).isNotEqualTo(Double.NaN);
  }

  @Test
  void shouldHandleZeroInDivision() {
    assertThat(1.0 / 0.0).isEqualTo(Double.POSITIVE_INFINITY);
    assertThat(-1.0 / 0.0).isEqualTo(Double.NEGATIVE_INFINITY);
    assertThat(0.0 / 0.0).isNaN();
  }
}

日期/时间边界测试

class DateTimeBoundaryTest {

  @Test
  void shouldHandleMinAndMaxDates() {
    LocalDate min = LocalDate.MIN;
    LocalDate max = LocalDate.MAX;

    assertThat(min).isBefore(max);
    assertThat(DateUtils.isValid(min)).isTrue();
    assertThat(DateUtils.isValid(max)).isTrue();
  }

  @Test
  void shouldHandleLeapYearBoundary() {
    LocalDate leapYearEnd = LocalDate.of(2024, 2, 29);
    assertThat(leapYearEnd).isNotNull();
  }

  @Test
  void shouldRejectInvalidDateInNonLeapYear() {
    assertThatThrownBy(() -> LocalDate.of(2023, 2, 29))
      .isInstanceOf(DateTimeException.class);
  }
}

数组索引边界测试

class ArrayBoundaryTest {

  @Test
  void shouldHandleFirstElementAccess() {
    int[] array = {1, 2, 3, 4, 5};
    assertThat(array[0]).isEqualTo(1);
  }

  @Test
  void shouldHandleLastElementAccess() {
    int[] array = {1, 2, 3, 4, 5};
    assertThat(array[array.length - 1]).isEqualTo(5);
  }

  @Test
  void shouldThrowOnNegativeIndex() {
    int[] array = {1, 2, 3};
    assertThatThrownBy(() -> array[-1])
      .isInstanceOf(ArrayIndexOutOfBoundsException.class);
  }

  @Test
  void shouldThrowOnOutOfBoundsIndex() {
    int[] array = {1, 2, 3};
    assertThatThrownBy(() -> array[10])
      .isInstanceOf(ArrayIndexOutOfBoundsException.class);
  }

  @Test
  void shouldHandleEmptyArray() {
    int[] empty = {};
    assertThat(empty.length).isZero();
    assertThatThrownBy(() -> empty[0])
      .isInstanceOf(ArrayIndexOutOfBoundsException.class);
  }
}

最佳实践

  • 明确测试边界:不要依赖随机测试
  • 将空值和空字符串与有效输入分开测试
  • 对多个边界情况使用参数化测试
  • 测试边界两侧(正下方、边界处、正上方)
  • 验证无效边界输入的错误消息
  • 记录特定边界对你的领域为何重要
  • 测试所有数值运算的溢出/下溢

限制和警告

  • 整数溢出:使用 Math.addExact() 检测静默溢出
  • 浮点精度:切勿使用精确相等判断;始终使用基于容差的断言
  • NaN 行为:NaN != NaN;使用 Float.isNaN() 或 Double.isNaN()
  • 集合大小限制:大型测试集合需注意内存占用
  • 字符串编码:使用 Unicode 字符进行国际化测试
  • 日期/时间边界:考虑时区转换和夏令时
  • 数组索引:始终测试索引 0、length-1 和越界情况

参考资料

评论

支持 Markdown