Files
wenzi/AI_TESTING_QUICK_FIX_GUIDE.md
Your Name 91a0b77f7a test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl
- 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE
- 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查
- 所有1266个测试用例通过
- 覆盖率: 指令81.89%, 行88.48%, 分支51.55%

docs: 添加项目状态报告
- 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态
- 包含质量指标、已完成功能、待办事项和技术债务
2026-03-02 13:31:54 +08:00

15 KiB
Raw Blame History

AI测试常见问题速查表

基于蚊子项目1210+测试经验 快速识别和修复AI生成测试的常见问题


🚨 第一类:虚假测试(立即修复)

1.1 测试框架本身

错误示例AI常生成

@Test
void shouldReturnName_whenGetName() {
    User user = new User("John");
    assertEquals("John", user.getName());  // 测试了Lombok
}

@Test
void shouldSetAge_whenSetAge() {
    User user = new User();
    user.setAge(25);
    assertEquals(25, user.getAge());  // 测试了setter
}

正确做法

// 不测试getter/setter除非有自定义逻辑
// 删除或合并为参数化测试

@ParameterizedTest
@CsvSource({
    "John, 25, active",
    "Jane, 30, inactive"
})
void shouldCreateUser_withValidData(String name, int age, String status) {
    User user = User.builder()
        .name(name)
        .age(age)
        .status(status)
        .build();
    
    assertThat(user.getName()).isEqualTo(name);
    assertThat(user.getAge()).isEqualTo(age);
    assertThat(user.getStatus()).isEqualTo(status);
}

检查方法

# 统计getter/setter测试数量
grep -r "void should.*when[Gg]et\|void should.*when[Ss]et" src/test/java | wc -l

# 如果>20个需要清理

1.2 虚假断言

错误示例(无意义断言)

@Test
void shouldProcessOrder() {
    Order result = orderService.process(orderRequest);
    
    assertNotNull(result);                    // 太弱
    assertNotNull(result.getId());           // 还是太弱
    assertTrue(result.getTotal() > 0);       // 不够具体
    // 缺少:验证具体金额、状态、库存扣减
}

@Test
void shouldValidateInput() {
    // 没有实际验证
    assertTrue(true);  // 总是通过!
}

正确做法

@Test
void shouldCalculateTotalCorrectly_whenProcessingOrder() {
    // Given
    OrderRequest request = createOrderRequest(BigDecimal.valueOf(100), 2);
    
    // When
    Order result = orderService.process(request);
    
    // Then - 验证具体业务结果
    assertThat(result.getTotal())
        .isEqualByComparingTo(BigDecimal.valueOf(200));  // 精确验证
    assertThat(result.getStatus()).isEqualTo("PAID");
    assertThat(result.getItems()).hasSize(2);
    
    // 验证副作用
    verify(inventoryService).deductStock("SKU001", 2);
    verify(paymentService).charge(eq("USER001"), eq(BigDecimal.valueOf(200)));
    
    // 验证数据库状态
    Order saved = orderRepository.findById(result.getId()).orElseThrow();
    assertThat(saved.getCreatedAt()).isNotNull();
}

识别脚本

# 查找虚假断言
find src/test/java -name "*Test.java" -exec grep -l "assertTrue(true)\|assertNotNull(new" {} \;

# 统计assertNotNull使用率
find src/test/java -name "*Test.java" -exec grep -c "assertNotNull" {} \; | sort -rn | head -20

🎯 第二类:边界条件缺失(必须补充)

2.1 系统化边界矩阵

类型 必须测试的值 AI常遗漏
数值 MIN_VALUE, -1, 0, 1, MAX_VALUE 极大/极小值
字符串 null, "", "a", "最大长度", "特殊字符🔑" 超长、Unicode
集合 null, empty, 1 element, max size null、空列表
时间 MIN, epoch, now, MAX, null 边界时间
并发 1 thread, 10 threads, race condition 并发测试

错误示例(缺少边界)

@Test
void shouldCalculateDiscount() {
    BigDecimal price = BigDecimal.valueOf(100);
    String userType = "VIP";
    
    BigDecimal discount = calculator.calculate(price, userType);
    
    assertEquals(BigDecimal.valueOf(80), discount);  // 只测试正常值
}

正确做法(参数化边界测试)

@ParameterizedTest
@CsvSource({
    "100, VIP, 80",           // 正常VIP
    "100, NORMAL, 100",       // 正常普通用户
    "0, VIP, 0",              // 边界0价格
    "-10, VIP, -8",           // 边界:负数价格
    "100, null, 100",         // 边界null用户类型
    "100, UNKNOWN, 100",      // 边界:未知类型
    "999999999, VIP, 799999999"  // 边界:极大值
})
void shouldCalculateDiscount_withBoundaryValues(
        BigDecimal price, String userType, BigDecimal expected) {
    
    BigDecimal discount = calculator.calculate(price, userType);
    
    assertThat(discount).isEqualByComparingTo(expected);
}

2.2 自动生成边界测试的Prompt

请为这个类生成边界条件测试,使用参数化测试覆盖:

1. 数值边界MIN_VALUE, -1, 0, 1, MAX_VALUE
2. 字符串边界null, "", "a", 最大长度, 特殊字符🔑
3. 集合边界null, empty, 1 element, max size
4. 时间边界MIN, epoch, now, MAX

要求:
- 使用JUnit 5 @ParameterizedTest
- 每个边界值一个测试用例
- 验证异常处理
- 命名shouldHandleXxx_whenYxxIsBoundaryValue

类代码:
[粘贴代码]

🔧 第三类Mock使用不当重构

3.1 Mock决策树

需要Mock吗
├─ 是第三方外部服务?(支付/短信/邮件)
│  └─ 是 → 可以Mock
│
├─ 是基础设施?(数据库/缓存/消息队列)
│  └─ 是 → 用Testcontainers不要Mock
│
├─ 是核心业务逻辑Service/Controller
│  └─ 是 → 禁止Mock用真实实现
│
└─ 否 → 不Mock

错误示例过度Mock

@SpringBootTest
class OrderServiceTest {
    
    @MockBean
    private OrderRepository orderRepository;  // ❌ 不应该Mock Repository
    
    @MockBean
    private InventoryService inventoryService;  // ⚠️ 可以Spy
    
    @MockBean
    private PaymentService paymentService;  // ✅ 外部服务可以Mock
    
    @Test
    void shouldCreateOrder() {
        when(orderRepository.save(any())).thenReturn(mockOrder);  // 测试了Mock
        when(inventoryService.checkStock(any())).thenReturn(true);
        when(paymentService.charge(any(), any())).thenReturn(success);
        
        Order result = orderService.create(request);
        
        // 这个测试其实什么都没验证!
        assertNotNull(result);
    }
}

正确做法

@SpringBootTest
@Testcontainers
class OrderServiceTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();
    
    @Autowired
    private OrderRepository orderRepository;  // ✅ 真实Repository
    
    @SpyBean
    private InventoryService inventoryService;  // ✅ Spy部分方法
    
    @MockBean
    private PaymentService paymentService;  // ✅ 外部服务Mock
    
    @Test
    void shouldCreateOrder_andSaveToDatabase() {
        // Given
        OrderRequest request = createOrderRequest();
        when(paymentService.charge(any(), any())).thenReturn(success);
        
        // When
        Order result = orderService.create(request);
        
        // Then
        // 验证真实数据库写入
        Order saved = orderRepository.findById(result.getId()).orElseThrow();
        assertThat(saved.getStatus()).isEqualTo("PAID");
        assertThat(saved.getTotal()).isEqualByComparingTo(request.getTotal());
        
        // 验证真实调用外部服务
        verify(paymentService).charge(
            eq(request.getUserId()), 
            eq(request.getTotal())
        );
    }
}

Mock审计脚本

#!/bin/bash
# mock-audit.sh

echo "=== Mock审计报告 ==="

# 1. 统计MockBean数量
echo "1. MockBean统计"
grep -r "@MockBean" src/test/java --include="*.java" | wc -l

# 2. 检查Repository Mock
echo "2. Repository Mock应该为0"
grep -r "@MockBean.*Repository" src/test/java --include="*.java" | wc -l

# 3. 检查Service Mock
echo "3. Service Mock应该<5"
grep -r "@MockBean.*Service" src/test/java --include="*.java" | wc -l

# 4. 计算Mock比例
total_beans=$(grep -r "@MockBean\|@Autowired\|@SpyBean" src/test/java --include="*.java" | wc -l)
mock_beans=$(grep -r "@MockBean" src/test/java --include="*.java" | wc -l)
ratio=$((mock_beans * 100 / total_beans))
echo "4. Mock比例${ratio}%"

if [ $ratio -gt 50 ]; then
    echo "⚠️  Warning: Mock比例过高建议重构"
fi

📊 第四类:性能测试缺失(补充)

4.1 AI常忽视的性能测试

错误(没有性能测试)

@Test
void shouldProcessLargeFile() {
    byte[] largeFile = new byte[10 * 1024 * 1024]; // 10MB
    // 只测试功能,不测试性能
}

正确做法

@Test
void shouldProcessLargeFile_withinTimeLimit() {
    byte[] largeFile = createLargeFile(10 * 1024 * 1024); // 10MB
    
    long start = System.currentTimeMillis();
    
    ProcessResult result = fileProcessor.process(largeFile);
    
    long duration = System.currentTimeMillis() - start;
    
    // 性能断言
    assertThat(duration).isLessThan(1000); // 必须在1秒内完成
    assertThat(result).isNotNull();
    assertThat(result.getProcessedBytes()).isEqualTo(largeFile.length);
}

@Test
void shouldHandleConcurrentRequests() throws InterruptedException {
    int threadCount = 10;
    CountDownLatch latch = new CountDownLatch(threadCount);
    AtomicInteger successCount = new AtomicInteger(0);
    
    for (int i = 0; i < threadCount; i++) {
        new Thread(() -> {
            try {
                service.process(createRequest());
                successCount.incrementAndGet();
            } finally {
                latch.countDown();
            }
        }).start();
    }
    
    assertTrue(latch.await(5, TimeUnit.SECONDS));
    assertThat(successCount.get()).isEqualTo(threadCount);
}

📝 第五类:可读性/可维护性差(重构)

5.1 测试命名反模式

AI常生成

@Test  // ❌ 无法看出测试什么
void test1() { }

@Test  // ❌ 不清晰
void shouldWork() { }

@Test  // ❌ 没有Given/When
void createUserTest() { }

正确命名

// 格式should[预期行为]_when[条件/场景]

@Test
void shouldReturnUserDetails_whenUserExists() { }

@Test
void shouldThrowNotFoundException_whenUserDoesNotExist() { }

@Test
void shouldSendWelcomeEmail_whenNewUserRegisters() { }

// 复杂场景
@Test
void shouldCalculateDiscountedPrice_whenUserIsVIP_andOrderExceeds1000() { }

5.2 Given-When-Then结构

标准结构

@Test
void shouldDeactivateAccount_whenUserRequestsDeletion() {
    // Given - 准备数据和状态
    User user = createActiveUser("user123");
    given(userRepository.findById("user123")).willReturn(Optional.of(user));
    
    // When - 执行操作
    accountService.deactivateAccount("user123");
    
    // Then - 验证结果和副作用
    assertThat(user.isActive()).isFalse();
    verify(userRepository).save(user);
    verify(emailService).sendDeactivationConfirmation("user123");
    verify(cacheManager).evict("user:user123");
}

🔍 自动化检查清单

创建检查脚本

#!/bin/bash
# testing-quality-check.sh

echo "🧪 测试质量检查报告"
echo "===================="

# 1. 虚假测试检查
echo "1. 检查虚假测试..."
fake_tests=$(grep -r "assertTrue(true)\|assertFalse(false)\|assertNotNull(new" src/test/java --include="*.java" | wc -l)
echo "   找到 $fake_tests 个虚假断言"

# 2. 边界测试检查
echo "2. 检查边界条件..."
boundary_tests=$(grep -r "MIN_VALUE\|MAX_VALUE\|null.*empty" src/test/java --include="*.java" | wc -l)
echo "   边界测试: $boundary_tests"

# 3. Mock比例检查
echo "3. 检查Mock比例..."
total=$(find src/test/java -name "*Test.java" | wc -l)
mocks=$(grep -r "@MockBean" src/test/java --include="*.java" | wc -l)
if [ $total -gt 0 ]; then
    ratio=$((mocks * 100 / total))
    echo "   Mock比例: ${ratio}%"
fi

# 4. 参数化测试检查
echo "4. 检查参数化测试..."
param_tests=$(grep -r "@ParameterizedTest\|@CsvSource" src/test/java --include="*.java" | wc -l)
echo "   参数化测试: $param_tests"

# 5. 命名规范检查
echo "5. 检查测试命名..."
incorrect=$(grep -r "void test[0-9]*\|void shouldWork\|void .*Test()" src/test/java --include="*.java" | wc -l)
echo "   命名不规范: $incorrect"

echo ""
echo "===================="
echo "检查完成!"

检查清单模板

每次生成测试后检查

功能性

  • 测试验证真实业务逻辑,不只是框架代码
  • 每个测试至少2个有意义的断言
  • 测试包含边界条件null/空/极值)
  • 测试包含异常场景

质量

  • Mock比例 < 50%
  • Service/Controller未Mock
  • Repository使用Testcontainers
  • 分支覆盖率 > 60%

可维护性

  • 使用should_when命名格式
  • 使用Given-When-Then结构
  • 没有硬编码值
  • 参数化测试替代重复测试

性能/并发

  • 大对象处理有性能断言
  • 并发场景有测试
  • 没有资源泄漏

🎯 AI生成测试Prompt优化

原Prompt容易生成虚假测试

为UserService生成单元测试

优化后Prompt生成高质量测试

为UserService生成生产级单元测试要求

1. 使用真实数据库Testcontainers禁止Mock Repository
2. 每个测试至少2个断言验证返回值和副作用
3. 使用参数化测试覆盖边界条件null, 空值, 极值, 并发
4. 测试名使用should_when格式
5. 包含Given-When-Then结构
6. 验证异常场景和错误处理
7. 包含性能断言(执行时间<100ms
8. 不要测试getter/setter/构造函数

代码:
[粘贴代码]

生成测试时请思考:
- 这个测试在生产环境有用吗?
- 能发现真实bug吗
- 维护成本高吗?

📈 质量评分标准

维度 权重 优秀 良好 需改进
功能性 30% 无虚假测试,验证业务逻辑 少量框架测试 大量getter/setter测试
边界覆盖 25% 系统化边界测试 常见边界测试 缺少边界测试
Mock使用 20% 比例<30%,核心真实 比例<50% 比例>70%
可维护性 15% 参数化,无重复 有重复但清晰 大量重复代码
性能/并发 10% 有性能+并发测试 有性能测试 无性能测试

评分

  • A级(90-100):生产就绪
  • B级(80-89):优秀
  • C级(70-79):良好,可优化
  • D级(60-69):及格,需改进
  • F级(<60):不合格,需重写

🆘 快速修复指南

问题1太多getter/setter测试

# 自动识别并标记
find src/test/java -name "*Test.java" -exec grep -l "shouldReturn.*whenGet\|shouldSet.*whenSet" {} \; | xargs -I {} echo "Review: {}"

问题2Mock比例过高

# 识别过度Mock的测试
find src/test/java -name "*Test.java" -exec sh -c 'count=$(grep -c "@MockBean" "$1"); if [ "$count" -gt 5 ]; then echo "$1: $count mocks"; fi' _ {} \;

问题3缺少边界测试

# 识别缺少边界测试的类
find src/test/java -name "*Test.java" -exec sh -c 'if ! grep -q "ParameterizedTest\|MIN_VALUE\|MAX_VALUE\|null" "$1"; then echo "Missing boundary: $1"; fi' _ {} \;

立即可用快速提升AI生成测试质量 🚀