关于 unit-test-utility-methods
面向开发者的测试技能,用于为工具类、静态辅助方法和纯函数编写单元测试,重点覆盖边界与极端情况,并避免不必要的模拟。
主要功能
- 01测试静态辅助方法与纯函数
- 02覆盖空值和空输入
- 03检查边界条件
- 04采用参数化测试模式
- 05纯工具函数避免使用模拟
适用场景
- 01测试字符串格式化与转换
- 02验证计算和类型转换
- 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/空输入的处理
- 测试集合或数组辅助方法
说明
- 创建测试类:根据实用工具命名(例如,
StringUtilsTest) - 测试正常路径:输入有效值,并验证预期输出
- 测试边界情况:null、空值、空白字符、单个元素
- 测试边界条件:最大/最小值、大数、精度
- 使用描述性名称:使用
shouldCapitalizeFirstLetter,而不是test1 - 使用 AssertJ:让断言更易读且可链式调用
- 使用
@ParameterizedTest:测试多个相似输入(参见references/parameterized-tests.md) - 避免模拟:纯实用工具无需模拟
示例
基本静态实用工具测试
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