🎯 今日核心问题

  1. AI 能生成什么级别的单测?边界和局限在哪?
  2. 如何让 AI 生成真正有价值的测试,而不只是 happy path?

📖 精读提纲

【一】核心概念

单测 = 行为验证,不是代码覆盖率游戏。AI 最擅长的恰恰是人最容易忽略的:边界 case 和异常 case。你写正常流程,让 AI 帮你想各种"如果……会怎样"。

【二】关键原理

给 AI 提供以下信息,单测质量会大幅提升:

【三】实战应用

示例:给 UserService.register() 生成单测

// 被测方法
public Long register(RegisterDTO dto) {
    // 1. 校验邮箱唯一
    if (userMapper.countByEmail(dto.getEmail()) > 0) {
        throw new BusinessException("邮箱已被注册");
    }
    // 2. 密码加密
    String encodedPwd = passwordEncoder.encode(dto.getPassword());
    // 3. 保存用户
    User user = new User();
    user.setEmail(dto.getEmail());
    user.setPassword(encodedPwd);
    user.setStatus(1);
    userMapper.insert(user);
    return user.getId();
}

Prompt:

"请为上面的 UserService.register() 方法生成 JUnit5 + Mockito 单测:
- 需要 Mock:userMapper, passwordEncoder
- 覆盖以下 case:
  1. 正常注册成功,返回用户ID
  2. 邮箱已存在,抛出 BusinessException
  3. 密码编码器被正确调用
  4. 用户保存时字段(email/status)正确设置
请使用 @ExtendWith(MockitoExtension.class),不要用 Spring 上下文"

AI 生成的单测示例结构:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock private UserMapper userMapper;
    @Mock private PasswordEncoder passwordEncoder;
    @InjectMocks private UserServiceImpl userService;

    @Test
    void register_success() {
        // given
        RegisterDTO dto = new RegisterDTO("test@example.com", "password123");
        when(userMapper.countByEmail(dto.getEmail())).thenReturn(0L);
        when(passwordEncoder.encode(dto.getPassword())).thenReturn("encoded_pwd");

        // when
        Long userId = userService.register(dto);

        // then
        assertNotNull(userId);
        verify(userMapper).insert(argThat(user ->
            "test@example.com".equals(user.getEmail()) &&
            "encoded_pwd".equals(user.getPassword()) &&
            user.getStatus() == 1
        ));
    }

    @Test
    void register_emailExists_throwsException() {
        // given
        when(userMapper.countByEmail(anyString())).thenReturn(1L);
        // when & then
        assertThrows(BusinessException.class,
            () -> userService.register(new RegisterDTO("exists@example.com", "pwd")));
    }
}

【四】常见误区


💡 今日金句

单测不是为了让覆盖率好看,而是为了让你改代码时有底气。AI 帮你想边界 case,你负责确认业务正确性。

✏️ 今日练习

选你项目里任意一个 Service 方法(有业务规则的那种,别选只是 CRUD 查询的),让 AI 生成覆盖以下所有类型的单测套件:

目标:让 AI 帮你一次生成一个可以直接运行的测试类。