# 测试最佳实践检查清单

> 基于蚊子项目1210+测试的真实经验总结
> 
> 本文档旨在帮助识别和规避AI生成测试中常见的陷阱，提升测试质量和可维护性。

## 📋 目录

1. [AI测试陷阱识别清单](#1-ai测试陷阱识别清单)
2. [测试质量评估矩阵](#2-测试质量评估矩阵)
3. [可维护性检查清单](#3-可维护性检查清单)
4. [性能测试最佳实践](#4-性能测试最佳实践)
5. [并发测试策略](#5-并发测试策略)
6. [测试命名与文档规范](#6-测试命名与文档规范)
7. [Mock使用准则](#7-mock使用准则)
8. [断言最佳实践](#8-断言最佳实践)
9. [边界条件系统化方法](#9-边界条件系统化方法)
10. [持续改进检查表](#10-持续改进检查表)

---

## 1. AI测试陷阱识别清单

### 1.1 过度测试框架本身 🚫

#### 问题描述
AI常生成测试Lombok生成的getter/setter、构造函数或框架代码的测试，这些测试毫无意义。

#### ❌ 错误示例
```java
@Test
void testGetterSetter() {
    User user = new User();
    user.setName("test");
    assertEquals("test", user.getName()); // 测试Lombok生成的方法
}

@Test
void testConstructor() {
    User user = new User("id", "name");
    assertNotNull(user); // 测试对象能被创建
    assertEquals("id", user.getId()); // 测试简单赋值
}
```

#### ✅ 正确示例
```java
@Test
void shouldDetectDuplicateUsers_whenRegisteringWithExistingEmail() {
    // 测试业务逻辑，而非框架功能
    userRepository.save(new User("existing@test.com"));
    
    DuplicateException exception = assertThrows(
        DuplicateException.class,
        () -> userService.register(new RegistrationRequest("existing@test.com"))
    );
    
    assertEquals("USER_EXISTS", exception.getErrorCode());
}
```

#### 检查方法
- [ ] 是否测试了框架生成的代码？
- [ ] 是否测试了无业务逻辑的简单赋值？
- [ ] 是否测试了编译器会保证的行为？

#### 自动化规则
```bash
# 查找可疑的DTO测试
grep -r "testGetter\|testSetter\|testConstructor" src/test --include="*.java"

# 查找Lombok类的无意义测试
find src/main -name "*.java" -exec grep -l "@Data\|@Getter\|@Setter" {} \; | \
  xargs -I {} basename {} .java | \
  xargs -I {} find src/test -name "{}Test.java" -exec grep -l "get.*()\|set.*()" {} \;
```

---

### 1.2 虚假断言 🚫

#### 问题描述
断言永远不会失败，或只验证显而易见的事情，无法真正验证业务逻辑。

#### ❌ 错误示例
```java
@Test
void testCreateObject() {
    Object obj = new Object();
    assertNotNull(obj); // 永远不会失败
}

@Test
void testTrueIsTrue() {
    assertTrue(true); // 无意义断言
}

@Test
void testServiceCalled() {
    service.doSomething();
    verify(service).doSomething(); // 只验证方法被调用
}
```

#### ✅ 正确示例
```java
@Test
void shouldCalculateCorrectReward_whenUserCompletesHighValueActivity() {
    // Given
    Activity activity = Activity.builder()
        .rewardPoints(100)
        .multiplier(2.0)
        .build();
    
    // When
    Reward reward = rewardCalculator.calculate(activity, user);
    
    // Then
    assertEquals(200, reward.getPoints()); // 验证实际业务值
    assertEquals("PREMIUM", reward.getTier()); // 验证业务状态
    assertNotNull(reward.getAwardedAt()); // 验证副作用
}
```

#### 检查方法
- [ ] 断言是否可能失败？
- [ ] 是否验证了具体的业务值？
- [ ] 是否验证了状态变化？

#### 自动化规则
```bash
# 查找虚假断言
grep -r "assertNotNull(new\|assertTrue(true\|assertFalse(false)" src/test --include="*.java"

# 查找只验证调用的测试
grep -r "verify(.*)\.doSomething\|verify(.*)\.save\|verify(.*)\.find" src/test --include="*.java" | \
  grep -v "assert\|verify.*times"
```

---

## 2. 测试质量评估矩阵

### 2.1 质量维度评分表

| 维度 | 权重 | 优秀(5) | 良好(4) | 合格(3) | 较差(2) | 不合格(1) |
|------|------|---------|---------|---------|---------|-----------|
| **业务价值** | 30% | 验证核心业务流程 | 验证重要业务规则 | 验证一般功能 | 验证边缘功能 | 无业务价值 |
| **断言质量** | 25% | 多维度精确验证 | 验证具体业务值 | 基本断言 | 模糊断言 | 虚假断言 |
| **边界覆盖** | 20% | 全面边界测试 | 主要边界覆盖 | 部分边界 | 极少边界 | 无边界测试 |
| **可读性** | 15% | 自文档化+注释 | 清晰命名结构 | 基本可读 | 难以理解 | 无法维护 |
| **独立性** | 10% | 完全独立 | 偶尔共享setup | 部分依赖 | 强依赖 | 相互依赖 |

### 2.2 测试分级标准

```
A级 (90-100分): 生产级测试，值得作为范例
B级 (75-89分): 良好测试，可以接受
C级 (60-74分): 及格测试，需要改进
D级 (40-59分): 问题测试，必须重构
F级 (<40分): 无效测试，应该删除
```

### 2.3 评估检查清单

- [ ] 测试是否明确验证了业务需求？
- [ ] 失败时能否快速定位问题？
- [ ] 是否覆盖了正常、异常、边界三种情况？
- [ ] 新开发者能否理解测试意图？
- [ ] 修改被测代码后，测试能否正确失败？
- [ ] 测试执行时间是否合理（<1秒）？
- [ ] 是否存在测试间的隐式依赖？

### 2.4 自动化评估脚本

```bash
#!/bin/bash
# test-quality-check.sh

echo "=== 测试质量快速评估 ==="

# 1. 检查虚假断言
echo "1. 虚假断言检查:"
grep -r "assertTrue(true)\|assertFalse(false)\|assertNotNull(new" src/test --include="*.java" | wc -l
echo "   发现疑似虚假断言数量"

# 2. 检查getter/setter测试
echo "2. Getter/Setter测试检查:"
grep -r "testGetter\|testSetter\|getId()\|setName" src/test --include="*.java" | wc -l
echo "   发现疑似框架测试数量"

# 3. 检查测试复杂度
echo "3. 测试复杂度检查:"
find src/test -name "*Test.java" -exec wc -l {} \; | sort -n | tail -5
echo "   最长的5个测试文件"

# 4. 检查断言密度
echo "4. 断言密度检查:"
for file in $(find src/test -name "*Test.java" | head -20); do
    tests=$(grep -c "@Test" "$file" 2>/dev/null || echo 0)
    asserts=$(grep -c "assert" "$file" 2>/dev/null || echo 0)
    if [ "$tests" -gt 0 ]; then
        density=$(echo "scale=2; $asserts / $tests" | bc)
        echo "   $file: $asserts assertions / $tests tests = $density"
    fi
done
```

---

## 3. 可维护性检查清单

### 3.1 硬编码值问题

#### 问题描述
测试中使用魔法数字和字符串，导致测试脆弱且难以理解。

#### ❌ 错误示例
```java
@Test
void testCalculation() {
    assertEquals(1000, service.calculate(500, 2)); // 魔法数字
    assertEquals("ERROR_001", exception.getCode()); // 无意义错误码
}
```

#### ✅ 正确示例
```java
class RewardCalculationTest {
    private static final int BASE_POINTS = 500;
    private static final int MULTIPLIER = 2;
    private static final int EXPECTED_REWARD = 1000;
    private static final String ERROR_INVALID_MULTIPLIER = "ERR_MULTIPLIER_NEGATIVE";
    
    @Test
    void shouldCalculateReward_whenValidMultiplierProvided() {
        int result = service.calculate(BASE_POINTS, MULTIPLIER);
        assertEquals(EXPECTED_REWARD, result);
    }
}
```

#### 检查方法
- [ ] 所有字面量是否有明确含义？
- [ ] 是否使用了有意义的常量名？
- [ ] 魔法数字是否分散在测试各处？

---

### 3.2 重复代码问题

#### 问题描述
多个测试中重复相同的setup和断言逻辑，难以维护。

#### ❌ 错误示例
```java
@Test
void testCreateUser() {
    User user = new User();
    user.setName("test");
    user.setEmail("test@test.com");
    user.setCreatedAt(LocalDateTime.now());
    // ... 重复10次
}

@Test
void testUpdateUser() {
    User user = new User();
    user.setName("test");
    user.setEmail("test@test.com");
    user.setCreatedAt(LocalDateTime.now());
    // ... 同样的重复
}
```

#### ✅ 正确示例
```java
class UserServiceTest {
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = createDefaultUser();
    }
    
    private User createDefaultUser() {
        return User.builder()
            .name("Test User")
            .email("test@test.com")
            .createdAt(LocalDateTime.now())
            .build();
    }
    
    private void assertUserHasDefaultValues(User user) {
        assertAll("User default values",
            () -> assertEquals("Test User", user.getName()),
            () -> assertEquals("test@test.com", user.getEmail()),
            () -> assertNotNull(user.getCreatedAt())
        );
    }
}
```

#### 检查方法
- [ ] 是否使用了@BeforeEach/@BeforeAll？
- [ ] 是否有测试数据工厂类？
- [ ] 是否有共享的断言方法？

---

### 3.3 测试间依赖问题

#### 问题描述
测试之间存在隐式依赖，执行顺序影响结果，难以并行运行。

#### ❌ 错误示例
```java
@Test
@Order(1) // 强制顺序本身就是问题信号
void testCreate() {
    userService.createUser("test");
}

@Test
@Order(2) // 依赖testCreate
void testRead() {
    User user = userService.findByName("test"); // 依赖前面的测试数据
    assertNotNull(user);
}
```

#### ✅ 正确示例
```java
@Test
void shouldCreateAndRetrieveUser_independently() {
    // Given - 每个测试自己准备数据
    String uniqueName = "test_" + UUID.randomUUID();
    
    // When
    userService.createUser(uniqueName);
    User created = userService.findByName(uniqueName);
    
    // Then
    assertNotNull(created);
    
    // Cleanup - 自己清理
    userService.delete(created.getId());
}
```

#### 检查方法
- [ ] 是否使用了@Order注解？（危险信号）
- [ ] 测试能否独立运行？
- [ ] 是否共享可变状态？
- [ ] 能否并行执行（`mvn test -Dparallel`）？

---

## 4. 性能测试最佳实践

### 4.1 性能测试类型

```java
/**
 * 1. 基线测试 - 记录正常性能指标
 */
@Test
void shouldCompleteWithinBaseline_whenProcessingStandardLoad() {
    // Given
    List<Data> standardData = DataFactory.create(100); // 标准数据量
    
    // When
    long start = System.currentTimeMillis();
    Result result = processor.process(standardData);
    long duration = System.currentTimeMillis() - start;
    
    // Then
    assertTrue(duration < 500, // 基线: <500ms
        "Processing took " + duration + "ms, expected <500ms");
    assertNotNull(result);
}

/**
 * 2. 负载测试 - 测试预期峰值
 */
@Test
void shouldHandlePeakLoad_whenConcurrentRequestsArrive() {
    int concurrentUsers = 100;
    int requestsPerUser = 10;
    
    ExecutorService executor = Executors.newFixedThreadPool(concurrentUsers);
    CountDownLatch latch = new CountDownLatch(concurrentUsers * requestsPerUser);
    AtomicInteger successCount = new AtomicInteger(0);
    
    // When
    for (int i = 0; i < concurrentUsers; i++) {
        executor.submit(() -> {
            for (int j = 0; j < requestsPerUser; j++) {
                try {
                    Response response = api.call();
                    if (response.isSuccess()) {
                        successCount.incrementAndGet();
                    }
                } finally {
                    latch.countDown();
                }
            }
        });
    }
    
    // Then
    assertTrue(latch.await(30, TimeUnit.SECONDS));
    assertEquals(concurrentUsers * requestsPerUser, successCount.get());
    
    executor.shutdown();
}

/**
 * 3. 压力测试 - 测试系统极限
 */
@Test
void shouldDegradeGracefully_whenExceedingCapacity() {
    // 逐步增加负载直到系统出现性能下降
    for (int load : Arrays.asList(100, 500, 1000, 2000, 5000)) {
        PerformanceResult result = runWithLoad(load);
        
        if (result.getErrorRate() > 0.05) { // 5%错误率阈值
            // 记录最大容量
            log.info("System capacity limit: {} requests", load);
            assertTrue(result.isGracefulDegradation(), 
                "System should degrade gracefully");
            break;
        }
    }
}

/**
 * 4. 稳定性测试 - 长时间运行
 */
@Test
void shouldMaintainPerformance_overExtendedPeriod() {
    long testDuration = TimeUnit.MINUTES.toMillis(5);
    long startTime = System.currentTimeMillis();
    List<Long> responseTimes = new ArrayList<>();
    
    while (System.currentTimeMillis() - startTime < testDuration) {
        long callStart = System.currentTimeMillis();
        api.call();
        responseTimes.add(System.currentTimeMillis() - callStart);
        
        Thread.sleep(100); // 模拟真实间隔
    }
    
    // 分析响应时间趋势
    double avgResponseTime = responseTimes.stream()
        .mapToLong(Long::longValue)
        .average()
        .orElse(0);
    
    double p95ResponseTime = calculatePercentile(responseTimes, 95);
    
    assertTrue(avgResponseTime < 200, "Average response time too high");
    assertTrue(p95ResponseTime < 500, "P95 response time too high");
}
```

### 4.2 性能测试检查清单

- [ ] 是否建立了性能基线？
- [ ] 是否测试了预期峰值负载？
- [ ] 是否测试了系统极限（压力测试）？
- [ ] 是否进行了长时间稳定性测试？
- [ ] 是否测量了响应时间分布（P50, P95, P99）？
- [ ] 是否监控了资源使用（CPU, 内存, IO）？
- [ ] 是否有性能回归检测机制？

### 4.3 性能反模式

```java
// ❌ 在单元测试中测试性能
@Test
void testPerformance() { // 错误：单元测试不该测性能
    for (int i = 0; i < 1000000; i++) {
        service.doSomething();
    }
}

// ❌ 不稳定的性能断言
@Test
void testResponseTime() {
    long start = System.currentTimeMillis();
    service.call();
    long duration = System.currentTimeMillis() - start;
    assertTrue(duration < 10); // 太严格，环境差异会导致失败
}

// ❌ 只测试吞吐量不测试延迟
@Test
void testThroughput() {
    int count = 0;
    long end = System.currentTimeMillis() + 1000;
    while (System.currentTimeMillis() < end) {
        service.call();
        count++;
    }
    assertTrue(count > 1000); // 只关心数量不关心单个请求质量
}
```

---

## 5. 并发测试策略

### 5.1 并发测试基本原则

```java
/**
 * 并发测试黄金法则:
 * 1. 不要依赖Thread.sleep() - 使用CountDownLatch/CyclicBarrier
 * 2. 不要假设执行顺序 - 使用同步原语控制
 * 3. 测试竞争条件，而不仅仅是并发执行
 * 4. 验证最终一致性和不变量
 */
```

### 5.2 常见并发测试模式

```java
/**
 * 模式1: 测试竞态条件 - 库存扣减
 */
@Test
void shouldPreventOverselling_whenConcurrentPurchases() throws InterruptedException {
    // Given
    Product product = productRepository.save(
        Product.builder().stock(10).build()
    );
    int concurrentBuyers = 15;
    ExecutorService executor = Executors.newFixedThreadPool(concurrentBuyers);
    CountDownLatch startLatch = new CountDownLatch(1);
    CountDownLatch completeLatch = new CountDownLatch(concurrentBuyers);
    AtomicInteger successCount = new AtomicInteger(0);
    AtomicInteger failCount = new AtomicInteger(0);
    
    // When - 所有线程同时开始
    for (int i = 0; i < concurrentBuyers; i++) {
        executor.submit(() -> {
            try {
                startLatch.await(); // 等待统一信号
                try {
                    purchaseService.buy(product.getId(), 1);
                    successCount.incrementAndGet();
                } catch (OutOfStockException e) {
                    failCount.incrementAndGet();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                completeLatch.countDown();
            }
        });
    }
    
    startLatch.countDown(); // 同时触发所有线程
    completeLatch.await(10, TimeUnit.SECONDS);
    
    // Then - 验证不变量
    Product updated = productRepository.findById(product.getId()).orElseThrow();
    assertEquals(10, successCount.get(), "只有10个应该成功");
    assertEquals(5, failCount.get(), "5个应该失败");
    assertEquals(0, updated.getStock(), "库存应该为0");
}

/**
 * 模式2: 测试死锁
 */
@Test
void shouldNotDeadlock_whenNestedLockAcquisition() {
    Account account1 = new Account("A1", 1000);
    Account account2 = new Account("A2", 1000);
    
    ExecutorService executor = Executors.newFixedThreadPool(2);
    
    // 两个线程以不同顺序获取锁（经典的死锁场景）
    Future<?> future1 = executor.submit(() -> 
        transferService.transfer(account1, account2, 100));
    Future<?> future2 = executor.submit(() -> 
        transferService.transfer(account2, account1, 100));
    
    // 验证不会死锁
    assertDoesNotThrow(() -> {
        future1.get(5, TimeUnit.SECONDS);
        future2.get(5, TimeUnit.SECONDS);
    });
}

/**
 * 模式3: 测试可见性
 */
@Test
void shouldEnsureVisibility_whenMultipleThreadsReadWrite() 
        throws InterruptedException {
    ConcurrentProcessor processor = new ConcurrentProcessor();
    int writerCount = 5;
    int readerCount = 10;
    int iterations = 1000;
    
    ExecutorService executor = Executors.newFixedThreadPool(writerCount + readerCount);
    CountDownLatch latch = new CountDownLatch(writerCount + readerCount);
    AtomicInteger visibilityViolations = new AtomicInteger(0);
    
    // Writers
    for (int i = 0; i < writerCount; i++) {
        final int writerId = i;
        executor.submit(() -> {
            for (int j = 0; j < iterations; j++) {
                processor.write(writerId, j);
            }
            latch.countDown();
        });
    }
    
    // Readers
    for (int i = 0; i < readerCount; i++) {
        executor.submit(() -> {
            for (int j = 0; j < iterations; j++) {
                Data data = processor.read();
                // 验证读取的数据一致性
                if (!data.isConsistent()) {
                    visibilityViolations.incrementAndGet();
                }
            }
            latch.countDown();
        });
    }
    
    latch.await(30, TimeUnit.SECONDS);
    assertEquals(0, visibilityViolations.get(), 
        "不应该出现可见性违规");
}

/**
 * 模式4: 使用JCStress进行系统并发测试
 */
@JCStressTest
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "两者都看到完整写入")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "一个看到写入")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE, desc = "另一个看到写入")
@Outcome(id = "0, 0", expect = Expect.FORBIDDEN, desc = "都不看到写入是bug")
@State
public class VolatileVisibilityTest {
    volatile int x;
    volatile int y;
    
    @Actor
    public void actor1() {
        x = 1;
        y = 1;
    }
    
    @Actor
    public void actor2(II_Result r) {
        r.r1 = x;
        r.r2 = y;
    }
}
```

### 5.3 并发测试检查清单

- [ ] 是否测试了竞态条件？
- [ ] 是否验证了不变量（最终一致性）？
- [ ] 是否使用了同步原语而非Thread.sleep()？
- [ ] 是否考虑了不同的线程交错？
- [ ] 是否测试了超时场景？
- [ ] 是否验证了资源清理？
- [ ] 是否使用JCStress等工具进行压力测试？

---

## 6. 测试命名与文档规范

### 6.1 测试命名最佳实践

#### 命名格式对比

| 格式 | 示例 | 推荐度 |
|------|------|--------|
| **should_when** | `shouldThrowException_whenInvalidInput` | ⭐⭐⭐⭐⭐ |
| **Given_When_Then** | `GivenValidUser_WhenRegister_ThenSuccess` | ⭐⭐⭐⭐ |
| **method_condition_result** | `registerUser_withInvalidEmail_throwsException` | ⭐⭐⭐⭐ |
| **testMethod** | `testRegister` | ⭐⭐ 不推荐 |

#### ✅ 优秀命名示例

```java
// 业务场景清晰
@Test
void shouldSendNotification_whenUserCompletesMilestone()

// 边界条件明确
@Test
void shouldRejectRequest_whenEmailExceeds255Characters()

// 异常场景具体
@Test
void shouldThrowDuplicateKeyException_whenRegisteringExistingEmail()

// 状态转换明确
@Test
void shouldTransitionFromPendingToActive_whenPaymentConfirmed()

// 并发场景清晰
@Test
void shouldMaintainConsistency_whenConcurrentUpdatesToSameEntity()
```

#### ❌ 糟糕命名示例

```java
@Test
void test1() // 无意义

@Test
void testUser() // 过于笼统

@Test
void testRegisterUserSuccess() // 没有when部分

@Test
void test() // 完全无信息
```

### 6.2 Given-When-Then结构

```java
@Test
void shouldCalculateDiscountedPrice_whenPremiumMemberOnSale() {
    // ==================== GIVEN ====================
    // 准备测试数据
    User premiumUser = User.builder()
        .id("user-123")
        .membershipTier(MembershipTier.PREMIUM)
        .registrationDate(LocalDate.of(2020, 1, 1))
        .build();
    
    Product saleProduct = Product.builder()
        .id("prod-456")
        .basePrice(new BigDecimal("100.00"))
        .saleDiscount(new BigDecimal("0.20")) // 8折
        .build();
    
    // 配置mock
    when(userService.findById("user-123")).thenReturn(premiumUser);
    when(productService.findById("prod-456")).thenReturn(saleProduct);
    
    // ==================== WHEN ====================
    // 执行被测操作
    PriceCalculationResult result = priceCalculator.calculate(
        "user-123", 
        "prod-456",
        2 // 购买2件
    );
    
    // ==================== THEN ====================
    // 验证结果
    assertAll("价格计算验证",
        // 验证业务值
        () -> assertEquals(new BigDecimal("144.00"), result.getFinalPrice(), 
            "Premium用户应该享受折上折: 100*0.8*0.9*2"),
        
        // 验证折扣明细
        () -> assertEquals(2, result.getAppliedDiscounts().size()),
        () -> assertTrue(result.getAppliedDiscounts().contains("SALE_20%")),
        () -> assertTrue(result.getAppliedDiscounts().contains("PREMIUM_10%")),
        
        // 验证行为
        () -> verify(userService).findById("user-123"),
        () -> verify(productService).findById("prod-456")
    );
}
```

### 6.3 测试文档注释规范

```java
/**
 * 测试场景: 高并发下的库存扣减
 * 
 * 业务规则:
 * - 每个商品有固定库存
 * - 多用户同时购买时，不应该超卖
 * - 库存为0时，后续购买请求应该失败
 * 
 * 测试策略:
 * - 使用15个并发线程模拟15个用户同时购买
 * - 商品初始库存为10
 * - 验证最终只有10个购买成功
 * 
 * 相关需求: REQ-001, REQ-045
 * 关联缺陷: BUG-2024-1234
 * 
 * @see InventoryService#deductStock(String, int)
 * @since 1.2.0
 */
@Test
void shouldPreventOverselling_whenConcurrentPurchases() {
    // ... 测试实现
}
```

### 6.4 检查清单

- [ ] 测试名是否描述了行为和条件？
- [ ] 是否使用了should_when格式？
- [ ] 是否包含Given-When-Then结构注释？
- [ ] 复杂测试是否有场景说明注释？
- [ ] 是否引用了相关需求和缺陷？
- [ ] 是否使用了assertAll组合相关断言？

---

## 7. Mock使用准则

### 7.1 Mock决策树

```
是否使用Mock?
├── 是: 外部依赖（数据库、API、消息队列）
├── 是: 不可控组件（随机数、时间、网络）
├── 是: 尚未实现的依赖
├── 是: 测试需要特定异常场景
├── 否: 业务逻辑本身（不应该Mock）
├── 否: 简单值对象
└── 否: 可以实际初始化的轻量对象
```

### 7.2 Mock最佳实践

```java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    
    @Mock
    private PaymentGateway paymentGateway; // ✅ 外部服务
    
    @Mock
    private InventoryService inventoryService; // ✅ 复杂依赖
    
    @InjectMocks
    private OrderService orderService; // 被测对象，不Mock
    
    // ❌ 不应该Mock
    // @Mock
    // private PriceCalculator priceCalculator; // 核心业务逻辑
    
    @Test
    void shouldCompleteOrder_whenPaymentSucceeds() {
        // Given - 配置外部依赖
        when(paymentGateway.charge(any(PaymentRequest.class)))
            .thenReturn(PaymentResult.success("txn-123"));
        when(inventoryService.reserve(anyString(), anyInt()))
            .thenReturn(InventoryResult.success());
        
        // When - 执行被测业务
        OrderResult result = orderService.placeOrder(createValidOrder());
        
        // Then - 验证业务结果和交互
        assertEquals(OrderStatus.COMPLETED, result.getStatus());
        verify(paymentGateway).charge(argThat(req -> 
            req.getAmount().compareTo(new BigDecimal("100.00")) == 0
        ));
        verify(inventoryService).reserve("SKU-001", 2);
    }
}
```

### 7.3 Mock反模式

```java
// ❌ 反模式1: Mock被测对象本身
@Test
void testOrderService() {
    OrderService mockService = mock(OrderService.class);
    when(mockService.placeOrder(any())).thenReturn(mockResult);
    
    // 测试的是Mock，不是真实逻辑
    assertNotNull(mockService.placeOrder(new Order()));
}

// ❌ 反模式2: 过度使用ArgumentCaptor
@Test
void testWithExcessiveCaptors() {
    ArgumentCaptor<PaymentRequest> captor1 = ArgumentCaptor.forClass(PaymentRequest.class);
    ArgumentCaptor<String> captor2 = ArgumentCaptor.forClass(String.class);
    ArgumentCaptor<Integer> captor3 = ArgumentCaptor.forClass(Integer.class);
    
    // ... 测试代码 ...
    
    // 过度验证实现细节，而非业务结果
    verify(service).method(captor1.capture(), captor2.capture(), captor3.capture());
    assertEquals("value1", captor1.getValue().getField1());
    assertEquals("value2", captor2.getValue());
    assertEquals(42, captor3.getValue());
}

// ❌ 反模式3: Mock Repository层（应该是集成测试）
@Test
void testWithMockedRepository() {
    when(userRepository.findById("123")).thenReturn(Optional.of(mockUser));
    
    // 测试的是Mock返回的数据，不是真实数据库交互
    User user = userService.findById("123");
    assertEquals(mockUser.getName(), user.getName());
}

// ❌ 反模式4: 不验证Mock交互
@Test
void testWithoutVerification() {
    when(paymentGateway.charge(any())).thenReturn(success());
    
    orderService.placeOrder(order);
    
    // 缺少verify，不知道paymentGateway是否真的被调用
}

// ❌ 反模式5: 使用Mockito的一般匹配器测试具体值
@Test
void testWithGeneralMatchers() {
    // 错误: 使用any()代替具体值
    when(service.calculate(any(), any())).thenReturn(100);
    
    // 无法验证是否正确传递了参数
    assertEquals(100, service.calculate(5, 10)); // 实际应该验证5*10=50
    assertEquals(100, service.calculate(999, 999)); // 错误值也返回100
}
```

### 7.4 Mock检查清单

- [ ] 只Mock外部依赖，不Mock核心业务逻辑
- [ ] 配置Mock时使用具体的参数匹配
- [ ] 验证Mock的交互（verify）
- [ ] 使用ArgumentCaptor时保持简洁
- [ ] Repository层使用真实数据库（集成测试）
- [ ] 考虑使用@Spy部分Mock
- [ ] 使用BDDMockito（given/willReturn/then/should）提升可读性

---

## 8. 断言最佳实践

### 8.1 断言金字塔

```
                    /\
                   /  \
                  / 单 \
                 / 个  \
                / 断言 \     ← 简单场景
               /--------\
              /   多个   \
             /   断言     \    ← 常见场景
            /--------------\
           /    组合断言    \
          /   (assertAll)    \   ← 复杂场景
         /--------------------\
        /     自定义断言        \  ← 领域特定
       /------------------------\
```

### 8.2 断言选择指南

| 场景 | 推荐断言 | 示例 |
|------|---------|------|
| 相等性 | assertEquals | `assertEquals(expected, actual)` |
| 同一性 | assertSame | `assertSame(singleton, result)` |
| 非空 | assertNotNull | `assertNotNull(user.getId())` |
| 布尔 | assertTrue/False | `assertTrue(user.isActive())` |
| 异常 | assertThrows | `assertThrows(InvalidException.class, () -> ...)` |
| 集合 | assertIterableEquals | `assertIterableEquals(expected, actual)` |
| 多条件 | assertAll | `assertAll("user", () -> ..., () -> ...)` |
| 超时 | assertTimeout | `assertTimeout(Duration.ofSeconds(1), () -> ...)` |
| 浮点数 | assertEquals(delta) | `assertEquals(3.14, result, 0.01)` |

### 8.3 高质量断言示例

```java
@Test
void shouldCreateUser_withAllFieldsProperlySet() {
    User user = userService.createUser(new CreateUserRequest(
        "john.doe@example.com",
        "John Doe",
        LocalDate.of(1990, 5, 15)
    ));
    
    // ✅ 组合断言 - 一次性验证所有相关属性
    assertAll("用户创建验证",
        // 验证ID生成
        () -> assertNotNull(user.getId(), "用户ID应该被生成"),
        () -> assertTrue(user.getId().startsWith("USR"), "ID应该以USR开头"),
        
        // 验证基本字段
        () -> assertEquals("john.doe@example.com", user.getEmail()),
        () -> assertEquals("John Doe", user.getName()),
        
        // 验证派生字段
        () -> assertEquals(34, user.getAge(), "年龄应该根据生日计算"),
        
        // 验证默认值
        () -> assertEquals(UserStatus.PENDING, user.getStatus()),
        () -> assertNotNull(user.getCreatedAt()),
        () -> assertTrue(user.getCreatedAt().isBefore(LocalDateTime.now().plusSeconds(1))),
        
        // 验证关联数据
        () -> assertNotNull(user.getProfile()),
        () -> assertTrue(user.getRoles().contains(Role.USER))
    );
}

@Test
void shouldThrowValidationException_whenEmailIsMalformed() {
    InvalidRequestException exception = assertThrows(
        InvalidRequestException.class,
        () -> userService.createUser(new CreateUserRequest(
            "invalid-email",
            "John Doe",
            null
        )),
        "应该抛出验证异常"
    );
    
    // ✅ 验证异常详情
    assertAll("异常验证",
        () -> assertEquals("VALIDATION_ERROR", exception.getErrorCode()),
        () -> assertTrue(exception.getMessage().contains("email")),
        () -> assertEquals(1, exception.getFieldErrors().size()),
        () -> assertEquals("email", exception.getFieldErrors().get(0).getField())
    );
}

@Test
void shouldReturnPagedResults_withCorrectPaginationMetadata() {
    Page<User> page = userService.findAll(PageRequest.of(2, 10)); // 第3页，每页10条
    
    // ✅ 验证复杂对象的所有方面
    assertAll("分页结果验证",
        // 内容验证
        () -> assertEquals(10, page.getContent().size(), "应该返回10条记录"),
        () -> assertTrue(page.getContent().stream().allMatch(u -> u.getId() != null)),
        
        // 分页元数据验证
        () -> assertEquals(2, page.getNumber(), "当前页码"),
        () -> assertEquals(10, page.getSize(), "每页大小"),
        () -> assertEquals(10, page.getNumberOfElements(), "当前页元素数"),
        
        // 导航验证
        () -> assertTrue(page.hasPrevious(), "应该有上一页"),
        () -> assertTrue(page.hasNext(), "应该有下一页"),
        () -> assertEquals(5, page.getTotalPages(), "总页数"),
        () -> assertEquals(47, page.getTotalElements(), "总元素数"),
        
        // 排序验证
        () -> assertTrue(page.getSort().isSorted()),
        () -> assertEquals("createdAt", page.getSort().iterator().next().getProperty())
    );
}
```

### 8.4 断言反模式

```java
// ❌ 反模式1: 模糊消息
assertTrue(user.isValid(), "验证失败"); // 消息无用
assertEquals(expected, actual); // 无消息，失败时难调试

// ✅ 正确
assertTrue(user.isValid(), "用户应该有效，但实际状态: " + user.getStatus());
assertEquals(expected, actual, "订单总额应该匹配，订单ID: " + orderId);

// ❌ 反模式2: 多个独立assert
@Test
void testUser() {
    assertNotNull(user);
    assertEquals("John", user.getName());
    assertTrue(user.isActive());
    // 第一个失败，后面的不会执行，不知道还有多少问题
}

// ✅ 正确 - 使用assertAll
@Test
void testUser() {
    assertAll("用户验证",
        () -> assertNotNull(user),
        () -> assertEquals("John", user.getName()),
        () -> assertTrue(user.isActive())
    );
}

// ❌ 反模式3: 在测试中try-catch
@Test
void testWithTryCatch() {
    try {
        service.doSomething();
        fail("应该抛出异常"); // 容易忘记
    } catch (Exception e) {
        assertEquals("error", e.getMessage());
    }
}

// ✅ 正确 - 使用assertThrows
@Test
void testWithAssertThrows() {
    Exception e = assertThrows(Exception.class, () -> service.doSomething());
    assertEquals("error", e.getMessage());
}

// ❌ 反模式4: 浮点数精确比较
assertEquals(0.1 + 0.2, result); // 可能失败: 0.30000000000000004

// ✅ 正确 - 使用delta
assertEquals(0.3, result, 0.001);
```

### 8.5 断言检查清单

- [ ] 断言消息是否清晰描述了期望？
- [ ] 是否使用assertAll组合相关断言？
- [ ] 异常测试是否使用了assertThrows？
- [ ] 浮点数比较是否使用了delta？
- [ ] 集合比较是否使用了适当的断言？
- [ ] 是否验证了所有重要的输出字段？
- [ ] 断言顺序是否合理（先验证前提条件）？

---

## 9. 边界条件系统化方法

### 9.1 边界值分析矩阵

```
输入类型          边界值                    测试用例
─────────────────────────────────────────────────────────
数值范围          最小值-1, 最小值,         -1, 0, 1, 99, 100, 101
                 最小值+1, 最大值-1,       (范围0-100)
                 最大值, 最大值+1
                 
字符串长度        空串, 1字符,              "", "a", "ab",
                 最大长度-1,               repeat("a", 254),
                 最大长度, 最大长度+1      repeat("a", 255),
                                          repeat("a", 256)
                                          
集合/数组         空, 1元素,                 [], [1], [1,2],
                 最大容量-1,               size=999, size=1000,
                 最大容量, 最大容量+1       size=1001
                 
日期时间          最小日期, 最大日期,        1970-01-01,
                 闰年2月29日,              2038-01-19 (Y2K38)
                 时区边界                  2024-02-29
                 
枚举值            第一个, 最后一个,          ENUM_FIRST,
                 随机中间值                ENUM_LAST,
                                          random choice
                                          
布尔/状态         true, false,              isActive=true,
                 无效状态值(如需要)         isActive=false
```

### 9.2 系统化边界测试实现

```java
/**
 * 边界测试策略实现
 */
class BoundaryTestStrategy {
    
    /**
     * 1. 数值边界测试
     */
    @ParameterizedTest
    @CsvSource({
        "-1, false",      // 低于最小值
        "0, true",        // 最小值
        "1, true",        // 最小值+1
        "99, true",       // 最大值-1
        "100, true",      // 最大值
        "101, false",     // 超过最大值
        "2147483647, false", // Integer.MAX_VALUE (溢出风险)
    })
    void shouldValidateRange_forParticipantCount(int count, boolean expectedValid) {
        ActivityRequest request = ActivityRequest.builder()
            .participantCount(count)
            .build();
        
        Set<ConstraintViolation<ActivityRequest>> violations = 
            validator.validate(request);
        
        boolean hasViolation = violations.stream()
            .anyMatch(v -> v.getPropertyPath().toString().equals("participantCount"));
        
        assertEquals(!expectedValid, hasViolation,
            "参与者数量 " + count + " 应该" + (expectedValid ? "有效" : "无效"));
    }
    
    /**
     * 2. 字符串边界测试
     */
    @ParameterizedTest
    @ValueSource(strings = {
        "",                                    // 空字符串
        "a",                                   // 1字符
        "ab",                                  // 2字符
        "a".repeat(254),                       // 最大长度-1
        "a".repeat(255),                       // 最大长度
        "a".repeat(256),                       // 最大长度+1
        "   ",                                 // 仅空白字符
        "test@example.com",                    // 有效格式
        "test",                                // 无效格式（无@）
        "test@",                               // 无效格式（无domain）
        "@example.com",                        // 无效格式（无local）
    })
    void shouldValidateEmail_forVariousInputs(String email) {
        UserRequest request = UserRequest.builder()
            .email(email)
            .build();
        
        boolean isValid = email != null && 
                         email.length() >= 5 && 
                         email.length() <= 255 &&
                         email.contains("@") &&
                         email.indexOf("@") > 0 &&
                         email.indexOf("@") < email.length() - 1;
        
        Set<ConstraintViolation<UserRequest>> violations = 
            validator.validate(request);
        
        assertEquals(!isValid, !violations.isEmpty(),
            "邮箱 '" + (email == null ? "null" : email.substring(0, Math.min(email.length(), 20))) + 
            "...' 验证结果应该匹配预期");
    }
    
    /**
     * 3. 集合边界测试
     */
    @Test
    void shouldHandleCollectionBoundaries() {
        // 空集合
        assertThrows(IllegalArgumentException.class, () ->
            batchProcessor.processBatch(Collections.emptyList())
        );
        
        // 单元素
        List<Item> singleItem = Collections.singletonList(new Item("1"));
        BatchResult result1 = batchProcessor.processBatch(singleItem);
        assertEquals(1, result1.getProcessedCount());
        
        // 最大容量
        List<Item> maxItems = IntStream.range(0, 1000)
            .mapToObj(i -> new Item(String.valueOf(i)))
            .collect(Collectors.toList());
        BatchResult resultMax = batchProcessor.processBatch(maxItems);
        assertEquals(1000, resultMax.getProcessedCount());
        
        // 超过最大容量
        List<Item> overMaxItems = IntStream.range(0, 1001)
            .mapToObj(i -> new Item(String.valueOf(i)))
            .collect(Collectors.toList());
        assertThrows(BatchSizeExceededException.class, () ->
            batchProcessor.processBatch(overMaxItems)
        );
    }
    
    /**
     * 4. 时间边界测试
     */
    @ParameterizedTest
    @CsvSource({
        "1970-01-01T00:00:00Z, true",     // Epoch开始
        "2024-02-29T12:00:00Z, true",     // 闰年2月29日
        "2023-02-29T12:00:00Z, false",    // 非闰年2月29日（无效）
        "2038-01-19T03:14:07Z, true",     // Y2K38边界前
        "2038-01-19T03:14:08Z, false",    // Y2K38边界后（32位时间戳溢出）
        "2100-01-01T00:00:00Z, true",     // 远期日期
    })
    void shouldValidateDateTime_forBoundaryValues(
            String dateTimeStr, boolean expectedValid) {
        try {
            Instant instant = Instant.parse(dateTimeStr);
            boolean isValid = instant.isAfter(Instant.EPOCH) &&
                            instant.isBefore(Instant.parse("2100-01-01T00:00:00Z"));
            
            assertEquals(expectedValid, isValid,
                "日期时间 " + dateTimeStr + " 验证结果应该为 " + expectedValid);
        } catch (DateTimeParseException e) {
            assertFalse(expectedValid, 
                "无效的日期时间格式应该验证失败");
        }
    }
    
    /**
     * 5. null和特殊值测试
     */
    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {" ", "\t", "\n", "null", "NULL", "undefined"})
    void shouldHandleSpecialStringValues(String input) {
        // 测试系统对各种特殊字符串值的处理
        String sanitized = sanitizer.sanitize(input);
        
        assertNotNull(sanitized, "不应该返回null");
        assertFalse(sanitized.equals("null"), "不应该保留字符串'null'");
        assertFalse(sanitized.equals("undefined"), "不应该保留字符串'undefined'");
    }
}

/**
 * 边界测试数据生成器
 */
class BoundaryTestDataGenerator {
    
    static Stream<Arguments> provideIntegerBoundaries(int min, int max) {
        return Stream.of(
            Arguments.of(min - 1, false, "below-minimum"),
            Arguments.of(min, true, "minimum"),
            Arguments.of(min + 1, true, "minimum+1"),
            Arguments.of((min + max) / 2, true, "middle"),
            Arguments.of(max - 1, true, "maximum-1"),
            Arguments.of(max, true, "maximum"),
            Arguments.of(max + 1, false, "above-maximum")
        );
    }
    
    static Stream<String> provideStringBoundaries(int maxLength) {
        return Stream.of(
        "",                                    // 空
        "a",                                   // 1字符
        "a".repeat(maxLength / 2),            // 中等长度
        "a".repeat(maxLength - 1),            // 最大-1
        "a".repeat(maxLength),                // 最大
        "a".repeat(maxLength + 1),            // 最大+1
        "   ",                                 // 仅空白
        "\t\n\r",                              // 控制字符
        "<script>alert('xss')</script>",     // XSS尝试
        "DROP TABLE users;--",                // SQL注入尝试
        "日本語テスト",                         // Unicode
        "🔥🎉💯",                               // Emoji
        new String(new char[10000]).replace('\0', 'a') // 超长
        );
    }
}
```

### 9.3 边界条件检查清单

- [ ] 是否测试了最小值和最大值？
- [ ] 是否测试了最小值-1和最大值+1？
- [ ] 是否测试了空值（null, 空串, 空集合）？
- [ ] 是否测试了0值（除零风险）？
- [ ] 是否测试了负数（如果适用）？
- [ ] 是否测试了极大值（溢出风险）？
- [ ] 是否测试了特殊字符和Unicode？
- [ ] 是否测试了日期边界（闰年、时区）？
- [ ] 是否使用了@ParameterizedTest系统化测试？
- [ ] 是否测试了并发情况下的边界？

---

## 10. 持续改进检查表

### 10.1 定期审查计划

```
频率         审查内容                           负责人
─────────────────────────────────────────────────────────
每日         新失败的测试分析                    开发团队
每周         测试运行时间趋势分析                 技术负责人
每月         测试质量评分和覆盖率审查              QA团队
每季度        全量测试审查和重构计划                架构师团队
每年         测试策略评估和工具升级                技术委员会
```

### 10.2 测试健康度指标

```java
/**
 * 测试健康度评估脚本
 */
class TestHealthMetrics {
    
    /**
     * 1. 计算测试脆弱性指数
     */
    void calculateTestFragilityIndex() {
        Map<String, Integer> metrics = new HashMap<>();
        
        // 不稳定测试比例
        int flakyTests = countFlakyTests();
        int totalTests = countTotalTests();
        double flakyRatio = (double) flakyTests / totalTests;
        
        // 平均修复时间
        double avgFixTime = calculateAverageFixTime();
        
        // 失败频率
        double failureFrequency = calculateFailureFrequency();
        
        // 脆弱性指数 = 不稳定比例 * 0.4 + 修复时间因子 * 0.3 + 失败频率 * 0.3
        double fragilityIndex = flakyRatio * 0.4 + 
                               (avgFixTime / 24) * 0.3 + 
                               failureFrequency * 0.3;
        
        System.out.println("测试脆弱性指数: " + String.format("%.2f", fragilityIndex));
        System.out.println("  - 不稳定测试: " + flakyTests + "/" + totalTests);
        System.out.println("  - 平均修复时间: " + avgFixTime + "小时");
        System.out.println("  - 失败频率: " + String.format("%.2f", failureFrequency));
    }
    
    /**
     * 2. 测试ROI计算
     */
    void calculateTestROI() {
        // 投入
        double developmentTime = 120; // 小时
        double maintenanceTimePerMonth = 8; // 小时
        double infrastructureCost = 500; // 月度基础设施成本
        
        // 产出
        int bugsPrevented = 15; // 月度预防的缺陷数
        double avgBugCost = 20; // 平均缺陷修复成本（小时）
        int regressionBugsPrevented = 5;
        
        double totalInvestment = developmentTime + 
                                (maintenanceTimePerMonth * 12) + 
                                (infrastructureCost * 12 / 100); // 折算为小时
        double totalReturn = (bugsPrevented * avgBugCost * 12) + 
                            (regressionBugsPrevented * avgBugCost * 2 * 12); // 回归缺陷成本更高
        
        double roi = (totalReturn - totalInvestment) / totalInvestment * 100;
        System.out.println("测试ROI: " + String.format("%.1f%%", roi));
    }
}
```

### 10.3 改进行动清单

#### 立即行动（本周）

- [ ] 识别并标记所有不稳定测试
- [ ] 删除或重构虚假断言测试
- [ ] 修复最近的测试失败
- [ ] 更新测试文档

#### 短期行动（本月）

- [ ] 实施测试命名规范检查
- [ ] 添加边界条件测试覆盖率
- [ ] 优化慢速测试（>1秒）
- [ ] 建立测试基线性能指标
- [ ] 审查Mock使用并修复过度Mock

#### 中期行动（本季度）

- [ ] 实施并行测试执行
- [ ] 建立测试质量评分流程
- [ ] 添加并发测试套件
- [ ] 实施测试数据工厂模式
- [ ] 建立性能测试基线

#### 长期行动（本年度）

- [ ] 评估并升级测试框架
- [ ] 实施契约测试
- [ ] 建立测试可视化仪表板
- [ ] 建立测试驱动开发文化
- [ ] 实施AI测试生成质量门禁

### 10.4 自动化质量门禁

```yaml
# .github/workflows/test-quality-gate.yml
name: Test Quality Gate

on: [push, pull_request]

jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run Test Quality Checks
        run: |
          # 1. 检查虚假断言
          FAKE_ASSERTIONS=$(grep -r "assertTrue(true)\|assertFalse(false)" src/test --include="*.java" | wc -l)
          if [ $FAKE_ASSERTIONS -gt 0 ]; then
            echo "❌ 发现 $FAKE_ASSERTIONS 个虚假断言"
            exit 1
          fi
          
          # 2. 检查getter/setter测试
          DTO_TESTS=$(grep -r "testGetter\|testSetter" src/test --include="*.java" | wc -l)
          if [ $DTO_TESTS -gt 10 ]; then
            echo "❌ 发现 $DTO_TESTS 个疑似DTO框架测试"
            exit 1
          fi
          
          # 3. 检查测试覆盖率
          mvn jacoco:report
          COVERAGE=$(grep -o 'Total[^%]*%' target/site/jacoco/index.html | grep -o '[0-9]*%' | head -1)
          echo "当前覆盖率: $COVERAGE"
          
          # 4. 检查测试执行时间
          SLOW_TESTS=$(mvn test 2>&1 | grep -c "Time elapsed.*> 1 sec")
          if [ $SLOW_TESTS -gt 20 ]; then
            echo "⚠️ 发现 $SLOW_TESTS 个慢速测试"
          fi
          
          echo "✅ 测试质量门禁通过"
```

### 10.5 测试改进度量表

| 指标 | 基线 | 目标 | 当前 | 差距 |
|------|------|------|------|------|
| 虚假断言数量 | 50 | 0 | ? | 待评估 |
| DTO框架测试数量 | 30 | 0 | ? | 待评估 |
| 平均测试执行时间 | 2.5s | <1s | ? | 待评估 |
| 不稳定测试比例 | 8% | <2% | ? | 待评估 |
| 边界条件覆盖率 | 45% | >80% | ? | 待评估 |
| 并发测试数量 | 0 | >20 | ? | 待评估 |
| 性能测试覆盖 | 0% | >50% | ? | 待评估 |
| 测试命名规范率 | 60% | >95% | ? | 待评估 |

---

## 附录A: 快速参考卡

### 测试反模式速查表

| 反模式 | 检测方法 | 修复建议 |
|--------|----------|----------|
| 虚假断言 | `grep -r "assertTrue(true)"` | 删除或添加有意义的验证 |
| 测试框架 | `grep -r "testGetter\|testSetter"` | 删除DTO/Entity的框架测试 |
| 过度Mock | 检查Mock核心业务类 | 使用真实对象或Spy |
| 硬编码 | 查找魔法数字 | 提取为命名常量 |
| 测试依赖 | 检查@Order注解 | 移除依赖，独立准备数据 |
| 缺少边界 | 检查参数化测试 | 添加@ParameterizedTest |
| 慢测试 | 执行时间>1秒 | 优化或使用@Tag("slow") |
| 无文档 | 检查注释和命名 | 添加Given-When-Then |

### 代码审查检查清单（复制到PR模板）

```markdown
## 测试代码审查检查单

### 质量
- [ ] 测试验证了有意义的业务逻辑（非框架代码）
- [ ] 断言具体且有意义（非assertTrue(true)）
- [ ] 使用了组合断言assertAll验证多个条件
- [ ] 异常测试使用了assertThrows

### 边界
- [ ] 测试了null输入
- [ ] 测试了空集合/字符串
- [ ] 测试了最大值/最小值
- [ ] 测试了无效/异常输入

### 可维护性
- [ ] 测试名使用should_when格式
- [ ] 使用了Given-When-Then结构
- [ ] 无硬编码魔法值
- [ ] 重复的setup抽取为方法

### Mock
- [ ] 只Mock外部依赖
- [ ] 没有Mock核心业务逻辑
- [ ] 配置了具体的参数匹配
- [ ] 验证了重要的Mock交互

### 并发
- [ ] 并发场景使用了CountDownLatch
- [ ] 验证了不变量和最终一致性
- [ ] 考虑了竞态条件
- [ ] 测试可以并行执行
```

---

## 附录B: 推荐阅读

1. **《xUnit Test Patterns》** - Gerard Meszaros
2. **《Test Driven Development: By Example》** - Kent Beck  
3. **《Growing Object-Oriented Software, Guided by Tests》** - Freeman & Pryce
4. **JUnit 5官方文档**: https://junit.org/junit5/docs/current/user-guide/
5. **Mockito最佳实践**: https://site.mockito.org/

---

## 版本历史

| 版本 | 日期 | 修改内容 | 作者 |
|------|------|----------|------|
| 1.0 | 2026-02-03 | 初始版本，基于1210个测试的经验总结 | AI Assistant |

---

**如何使用本文档：**

1. **新开发人员**: 阅读第1、6、8章，了解基本规范
2. **代码审查**: 使用附录A的检查清单
3. **重构测试**: 参考第3、7、9章的具体方法
4. **建立规范**: 实施第10章的持续改进流程
5. **团队培训**: 分享第1章的AI陷阱识别，避免生成低质量测试

---

*本文档基于蚊子项目真实测试代码的经验教训编制，旨在建立高质量、可维护的测试文化。*
