mcpField
U
unit-test-utility-methods

unit-test-utility-methods

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

关于 unit-test-utility-methods

面向开发者的测试技能,用于为工具类、静态辅助方法和纯函数编写单元测试,重点覆盖边界与极端情况,并避免不必要的模拟。

主要功能

  1. 01测试静态辅助方法与纯函数
  2. 02覆盖空值和空输入
  3. 03检查边界条件
  4. 04采用参数化测试模式
  5. 05纯工具函数避免使用模拟

适用场景

  1. 01测试字符串格式化与转换
  2. 02验证计算和类型转换
  3. 03检查校验与集合辅助方法

安装

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

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

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

附带文件references/edge-cases.mdreferences/parameterized-tests.md

详细介绍

实用工具类和静态方法的单元测试

概述

此技能用于为包含静态辅助方法和纯函数的实用工具类生成测试。它提供了测试 null 处理、边界情况、边界条件以及常见工具(如字符串操作、计算、数据验证和集合)的模式。纯函数无需模拟。

适用场景

在以下情况下使用此技能:

  • 为包含静态方法的实用工具类/辅助类编写测试
  • 测试无状态且无副作用的纯函数
  • 测试字符串操作、格式化或转换工具
  • 测试计算、转换或数学辅助函数
  • 测试数据验证和格式化工具
  • 验证实用工具代码对 null/空输入的处理
  • 测试集合或数组辅助方法

说明

  1. 创建测试类:根据实用工具命名(例如,StringUtilsTest)
  2. 测试正常路径:输入有效值,并验证预期输出
  3. 测试边界情况:null、空值、空白字符、单个元素
  4. 测试边界条件:最大/最小值、大数、精度
  5. 使用描述性名称:使用 shouldCapitalizeFirstLetter,而不是 test1
  6. 使用 AssertJ:让断言更易读且可链式调用
  7. 使用 @ParameterizedTest:测试多个相似输入(参见 references/parameterized-tests.md)
  8. 避免模拟:纯实用工具无需模拟

示例

基本静态实用工具测试
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

class StringUtilsTest {

    @Test
    void shouldCapitalizeFirstLetter() {
        assertThat(StringUtils.capitalize("hello")).isEqualTo("Hello");
    }

    @Test
    void shouldReturnNullForNullInput() {
        assertThat(StringUtils.capitalize(null)).isNull();
    }

    @Test
    void shouldHandleEmptyString() {
        assertThat(StringUtils.capitalize("")).isEmpty();
    }

    @Test
    void shouldHandleSingleCharacter() {
        assertThat(StringUtils.capitalize("a")).isEqualTo("A");
    }
}
综合示例:isEmpty 实现
// Input: public static boolean isEmpty(String str)
//   { return str == null || str.trim().isEmpty(); }

class StringUtilsTest {

    @Test
    void shouldReturnTrueForNullString() {
        assertThat(StringUtils.isEmpty(null)).isTrue();
    }

    @Test
    void shouldReturnTrueForEmptyString() {
        assertThat(StringUtils.isEmpty("")).isTrue();
    }

    @Test
    void shouldReturnTrueForWhitespaceOnly() {
        assertThat(StringUtils.isEmpty("   ")).isTrue();
    }

    @Test
    void shouldReturnFalseForNonEmptyString() {
        assertThat(StringUtils.isEmpty("hello")).isFalse();
    }
}
Null 安全实用工具
class NullSafeUtilsTest {

    @Test
    void shouldReturnDefaultWhenNull() {
        assertThat(NullSafeUtils.getOrDefault(null, "default")).isEqualTo("default");
    }

    @Test
    void shouldReturnValueWhenNotNull() {
        assertThat(NullSafeUtils.getOrDefault("value", "default")).isEqualTo("value");
    }

    @Test
    void shouldReturnFalseWhenBlank() {
        assertThat(NullSafeUtils.isNotBlank(null)).isFalse();
        assertThat(NullSafeUtils.isNotBlank("   ")).isFalse();
    }
}
数学/计算实用工具
class MathUtilsTest {

    @Test
    void shouldCalculatePercentage() {
        assertThat(MathUtils.percentage(25, 100)).isEqualTo(25.0);
    }

    @Test
    void shouldHandleZeroDivisor() {
        assertThat(MathUtils.percentage(50, 0)).isZero();
    }

    @Test
    void shouldRoundToDecimalPlaces() {
        assertThat(MathUtils.round(3.14159, 2)).isEqualTo(3.14);
    }

    @Test
    void shouldHandleFloatingPointWithTolerance() {
        assertThat(MathUtils.multiply(0.1, 0.2))
            .isCloseTo(0.02, within(0.0001));
    }
}
集合实用工具
class CollectionUtilsTest {

    @Test
    void shouldFilterList() {
        List<Integer> result = CollectionUtils.filter(List.of(1, 2, 3, 4), n -> n % 2 == 0);
        assertThat(result).containsExactly(2, 4);
    }

    @Test
    void shouldHandleNullList() {
        assertThat(CollectionUtils.filter(null, n -> true)).isEmpty();
    }

    @Test
    void shouldJoinWithSeparator() {
        assertThat(CollectionUtils.join(List.of("a", "b", "c"), "-")).isEqualTo("a-b-c");
    }

    @Test
    void shouldDeduplicateList() {
        assertThat(CollectionUtils.deduplicate(List.of("a", "b", "a")))
            .containsExactlyInAnyOrder("a", "b");
    }
}
数据验证实用工具
class ValidatorUtilsTest {

    @Test
    void shouldValidateEmailFormat() {
        assertThat(ValidatorUtils.isValidEmail("[email protected]")).isTrue();
        assertThat(ValidatorUtils.isValidEmail("invalid")).isFalse();
    }

    @Test
    void shouldValidateUrlFormat() {
        assertThat(ValidatorUtils.isValidUrl("https://example.com")).isTrue();
        assertThat(ValidatorUtils.isValidUrl("not a url")).isFalse();
    }

    @Test
    void shouldValidateCreditCard() {
        assertThat(ValidatorUtils.isValidCreditCard("4532015112830366")).isTrue();
        assertThat(ValidatorUtils.isValidCreditCard("1234567890123456")).isFalse();
    }
}
具有 Clock 依赖的实用工具(少见)
@ExtendWith(MockitoExtension.class)
class DateUtilsTest {

    @Mock
    private Clock clock;

    @Test
    void shouldGetDateFromClock() {
        when(clock.instant()).thenReturn(Instant.parse("2024-01-15T10:00:00Z"));
        assertThat(DateUtils.today(clock)).isEqualTo(LocalDate.of(2024, 1, 15));
    }
}

最佳实践

  • 仅测试纯函数——不依赖副作用或状态
  • 覆盖正常路径和边界情况——null、空值、空白字符、极端值
  • 使用描述性测试名称——shouldReturnNullWhenInputIsNull
  • 对多个相似输入使用 @ParameterizedTest(参见 references/parameterized-tests.md)
  • 测试边界条件——最小/最大值、溢出、精度
  • 避免模拟纯函数——仅模拟 Clock 等外部依赖
  • 保持测试相互独立——测试之间不应存在顺序依赖

约束与警告

  • 不要模拟静态方法:仅在绝对必要时使用反射工具
  • 纯函数要求:有状态的实用工具更难测试;优先采用不可变设计
  • 浮点精度:切勿使用精确相等;使用 isCloseTo(delta)
  • Null 处理一致性:确定实用工具是返回 null 还是抛出异常,并据此测试
  • 线程安全:静态实用工具必须具备线程安全性;应单独验证并发行为
  • 不可变输入:说明实用工具是否会修改输入参数
  • 边界情况参考:参见 references/edge-cases.md,了解边界测试模式

评论

支持 Markdown