# AI测试常见问题速查表 基于蚊子项目1210+测试经验 快速识别和修复AI生成测试的常见问题 --- ## 🚨 第一类:虚假测试(立即修复) ### 1.1 测试框架本身 ❌ **错误示例**(AI常生成) ```java @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 } ``` ✅ **正确做法** ```java // 不测试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); } ``` **检查方法** ```bash # 统计getter/setter测试数量 grep -r "void should.*when[Gg]et\|void should.*when[Ss]et" src/test/java | wc -l # 如果>20个,需要清理 ``` --- ### 1.2 虚假断言 ❌ **错误示例**(无意义断言) ```java @Test void shouldProcessOrder() { Order result = orderService.process(orderRequest); assertNotNull(result); // 太弱 assertNotNull(result.getId()); // 还是太弱 assertTrue(result.getTotal() > 0); // 不够具体 // 缺少:验证具体金额、状态、库存扣减 } @Test void shouldValidateInput() { // 没有实际验证 assertTrue(true); // 总是通过! } ``` ✅ **正确做法** ```java @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(); } ``` **识别脚本** ```bash # 查找虚假断言 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 | 并发测试 | ❌ **错误示例**(缺少边界) ```java @Test void shouldCalculateDiscount() { BigDecimal price = BigDecimal.valueOf(100); String userType = "VIP"; BigDecimal discount = calculator.calculate(price, userType); assertEquals(BigDecimal.valueOf(80), discount); // 只测试正常值 } ``` ✅ **正确做法**(参数化边界测试) ```java @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 ```markdown 请为这个类生成边界条件测试,使用参数化测试覆盖: 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) ```java @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); } } ``` ✅ **正确做法** ```java @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审计脚本** ```bash #!/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常忽视的性能测试 ❌ **错误**(没有性能测试) ```java @Test void shouldProcessLargeFile() { byte[] largeFile = new byte[10 * 1024 * 1024]; // 10MB // 只测试功能,不测试性能 } ``` ✅ **正确做法** ```java @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常生成** ```java @Test // ❌ 无法看出测试什么 void test1() { } @Test // ❌ 不清晰 void shouldWork() { } @Test // ❌ 没有Given/When void createUserTest() { } ``` ✅ **正确命名** ```java // 格式:should[预期行为]_when[条件/场景] @Test void shouldReturnUserDetails_whenUserExists() { } @Test void shouldThrowNotFoundException_whenUserDoesNotExist() { } @Test void shouldSendWelcomeEmail_whenNewUserRegisters() { } // 复杂场景 @Test void shouldCalculateDiscountedPrice_whenUserIsVIP_andOrderExceeds1000() { } ``` ### 5.2 Given-When-Then结构 ✅ **标准结构** ```java @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"); } ``` --- ## 🔍 自动化检查清单 ### 创建检查脚本 ```bash #!/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测试 ```bash # 自动识别并标记 find src/test/java -name "*Test.java" -exec grep -l "shouldReturn.*whenGet\|shouldSet.*whenSet" {} \; | xargs -I {} echo "Review: {}" ``` ### 问题2:Mock比例过高 ```bash # 识别过度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:缺少边界测试 ```bash # 识别缺少边界测试的类 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生成测试质量!** 🚀