mcpField
U
unit-testing

unit-testing

github.com/folio-org测试与质量358 次安装1 天前更新
代码仓库

关于 unit-testing

面向使用 Java 和 Mockito 编写测试的开发者,提供单元测试规范,涵盖测试结构、命名、桩设定、验证及常见检查。

主要功能

  1. 01独立且自包含的测试
  2. 02安排—执行—断言结构
  3. 03Mockito 桩设定与验证指南
  4. 04命名和测试数据规范

适用场景

  1. 01为服务方法编写单元测试
  2. 02检查测试类中的 Mockito 用法

安装

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

$npx skills add https://github.com/folio-org/folio-eureka-ai-dev --skill unit-testing

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

附带文件references/examples.mdreferences/patterns.md

详细介绍

单元测试指南

一般原则

1. 测试独立性
  • 每个测试都必须独立且自包含
  • 测试不得依赖执行顺序
  • 尽可能在测试方法中使用局部变量,而不是共享实例变量
2. 清晰的测试名称

遵循以下模式:methodName_scenario_expectedBehavior

示例:tableExists_positive_tableIsPresent、deleteTimer_negative_timerDescriptorIdIsNull

3. 测试组织

每个测试都按以下结构编写:准备 → 执行 → 断言

Mockito 最佳实践

1. 绝不使用宽松模式

❌ 绝不要添加 @MockitoSettings(strictness = Strictness.LENIENT) —— 应编写精确的测试。

2. 避免不必要的桩

只为测试中实际使用的内容创建桩(when())。仅在需要它们的测试中调用辅助方法:

private void setupContextMocks() {
    when(context.getFolioModuleMetadata()).thenReturn(moduleMetadata);
    when(context.getTenantId()).thenReturn(TENANT_ID);
    when(moduleMetadata.getDBSchemaName(TENANT_ID)).thenReturn(SCHEMA_NAME);
}
3. 移除冗余的 verify 语句

只验证未通过 when() 模拟的交互:

verify(connection).close();        // ✅ not mocked
verify(dataSource).getConnection(); // ❌ redundant — already mocked
4. 优先使用具体对象桩,而不是 any() 匹配器

当测试数据由你控制时,使用具体对象:

when(mapper.convert(input)).thenReturn(output);           // ✅
when(mapper.convert(any(InputType.class))).thenReturn(output); // ❌

仅在以下情况下使用 any():服务会不可预测地修改对象、可能传入多个不同对象,或使用参数捕获器时。

5. 基本返回值优先使用简单的 when().thenReturn()
when(service.process(data)).thenReturn(true);       // ✅
doAnswer(inv -> true).when(service).process(data);  // ❌

仅在返回值需要计算、存在条件逻辑或需要副作用时使用 thenAnswer()。

6. 为完整的服务流程创建桩

为被测代码路径中的每个外部调用创建桩:

when(repository.findByKey(key)).thenReturn(Optional.empty());
when(repository.save(entity)).thenReturn(entity);
when(repository.findById(id)).thenReturn(Optional.of(entity));
7. 谨慎使用 verifyNoMoreInteractions

仅包含其交互应已全部核对的模拟对象:

@AfterEach
void tearDown() {
    verifyNoMoreInteractions(dataSource, connection, databaseMetaData, resultSet);
}

测试结构

1. 类设置
@UnitTest
@ExtendWith(MockitoExtension.class)
class MyServiceTest {

    private static final String CONSTANT_VALUE = "test-value";

    @Mock
    private Dependency1 dependency1;

    @AfterEach
    void tearDown() {
        verifyNoMoreInteractions(dependency1);
    }
}
2. 单个测试
@Test
void methodName_scenario_expectedBehavior() throws Exception {
    // Arrange
    setupCommonMocks();
    var service = new MyService(dependency1);
    when(dependency1.doSomething()).thenReturn(expectedValue);

    // Act
    var result = service.methodUnderTest();

    // Assert
    assertThat(result).isEqualTo(expectedValue);
    verify(dependency1).close();
}

验证模式

1. 成功操作
@Test
void operation_positive_success() throws Exception {
    setupRequiredMocks();
    when(repository.find()).thenReturn(entity);

    var result = service.operation();

    assertThat(result).isNotNull();
    verify(connection).close();
}
2. 异常场景
@Test
void operation_negative_throwsException() throws Exception {
    var expectedException = new SQLException("Connection failed");
    when(dataSource.getConnection()).thenThrow(expectedException);

    assertThatThrownBy(() -> service.operation())
        .isInstanceOf(DataRetrievalFailureException.class)
        .hasMessageContaining("Failed to perform operation")
        .hasCause(expectedException);
}

常见模式

1. 测试数据常量

在类级别定义,以便在多个测试中重复使用:

private static final String MODULE_ID = "mod-foo-1.0.0";
private static final String TENANT_ID = "test-tenant";
2. 辅助方法

当相同代码出现在 2 个或更多测试中时,将对象创建、断言、查找对象和模拟设置提取为 private static 方法。

有关高级模式(参数化测试、深拷贝桩、流式 API、精确匹配、显式 Lambda 类型),请参阅 references/patterns.md。

完整的测试类示例请参阅 references/examples.md。

检查清单

  • 没有 @MockitoSettings(strictness = Strictness.LENIENT)
  • 没有不必要的桩——所有 when() 语句都已使用
  • 只验证未模拟的方法
  • 测试数据可控时,使用具体对象桩,而不是 any()
  • 基本返回值使用简单的 when().thenReturn()
  • 代码路径中的所有仓库/服务交互都已创建桩
  • 服务创建内部副本时,为复制操作创建桩
  • 测试名称遵循 methodName_scenario_expectedBehavior
  • 每个测试都相互独立
  • 验证资源(连接、流)已关闭
  • 异常测试验证类型、消息和原因
  • 可重复使用的测试数据使用常量
  • 辅助方法消除了代码重复
  • 清晰划分准备、执行和断言部分
  • 使用流式 API 创建测试数据
  • 单个参数的参数化测试使用 Stream<Type>
  • var 推断失败时,为 Lambda 显式声明类型
  • 精确匹配测试包含相似但不匹配的值
  • 覆盖工具方法的所有错误路径
  • 移除未使用的导入

评论

支持 Markdown