第48课:AI 测试接口层面代码

覆盖知识点:KP-231 ~ KP-232

AI 自动按层切片测试

AI 需要理解项目的分层架构,并针对每一层生成对应的测试。这是从”AI 会写代码”到”AI 会验证代码”的关键能力。

flowchart TD
    subgraph 项目分层
        C[Controller 层] --> S[Service 层]
        S --> R[Repository 层]
        R --> DB[(Database)]
    end
    
    subgraph AI 测试生成
        TC[Controller Test] -->|Mock Service| C
        TS[Service Test] -->|Mock Repository| S
        TR[Repository Test] -->|测试 SQL| R
    end
    
    subgraph 验证目标
        V1[HTTP 端点可用性]
        V2[业务逻辑正确性]
        V3[数据访问正确性]
    end
    
    TC --> V1
    TS --> V2
    TR --> V3

Controller 层测试

验证目标

  • HTTP 端点全部可用,无遗漏
  • 请求参数校验正确
  • 响应状态码和格式符合约定
  • 异常场景返回正确的错误信息

AI 生成的示例

@WebMvcTest(OrderController.class)
class OrderControllerTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @MockitoBean
    private OrderService orderService;
 
    @Test
    void shouldCreateOrderSuccessfully() throws Exception {
        // Arrange
        CreateOrderRequest request = new CreateOrderRequest(1L, 100L, 2);
        given(orderService.createOrder(any(CreateOrderRequest.class)))
                .willReturn(new OrderResponse(1L, "CREATED"));
 
        // Act & Assert
        mockMvc.perform(post("/api/orders")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.status").value("CREATED"));
    }
 
    @Test
    void shouldReturn400WhenProductIdIsNull() throws Exception {
        // Arrange
        CreateOrderRequest request = new CreateOrderRequest(null, null, 0);
 
        // Act & Assert
        mockMvc.perform(post("/api/orders")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(request)))
                .andExpect(status().isBadRequest());
    }
}

Service 层测试

验证目标

  • 业务逻辑正确性(正常路径)
  • 边界条件和异常处理
  • 事务行为
  • 依赖协作的正确性

AI 生成的示例

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
 
    @Mock
    private OrderRepository orderRepository;
 
    @Mock
    private ProductService productService;
 
    @InjectMocks
    private OrderService orderService;
 
    @Test
    void shouldCreateOrderWhenProductIsAvailable() {
        // Arrange
        CreateOrderRequest request = new CreateOrderRequest(1L, 100L, 2);
        given(productService.checkStock(100L, 2)).willReturn(true);
        given(orderRepository.save(any(Order.class)))
                .willReturn(new Order(1L, 1L, 100L, 2, "CREATED", LocalDateTime.now()));
 
        // Act
        OrderResponse response = orderService.createOrder(request);
 
        // Assert
        assertThat(response.getStatus()).isEqualTo("CREATED");
        then(orderRepository).should().save(any(Order.class));
    }
 
    @Test
    void shouldThrowExceptionWhenStockInsufficient() {
        // Arrange
        CreateOrderRequest request = new CreateOrderRequest(1L, 100L, 999);
        given(productService.checkStock(100L, 999)).willReturn(false);
 
        // Act & Assert
        assertThrows(InsufficientStockException.class,
                () -> orderService.createOrder(request));
        then(orderRepository).should(never()).save(any());
    }
}

Repository 层测试

验证目标

  • SQL 语句正确性
  • 数据映射正确性
  • 查询结果完整性
  • 数据库约束正确性

AI 生成的示例

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
class OrderRepositoryTest {
 
    @Autowired
    private OrderRepository orderRepository;
 
    @Autowired
    private TestEntityManager entityManager;
 
    @Test
    void shouldFindOrdersByUserId() {
        // Arrange
        Order order1 = new Order(null, 1L, 100L, 2, "CREATED", LocalDateTime.now());
        Order order2 = new Order(null, 1L, 101L, 1, "PAID", LocalDateTime.now());
        Order order3 = new Order(null, 2L, 102L, 3, "CREATED", LocalDateTime.now());
        entityManager.persist(order1);
        entityManager.persist(order2);
        entityManager.persist(order3);
 
        // Act
        List<Order> userOrders = orderRepository.findByUserId(1L);
 
        // Assert
        assertThat(userOrders).hasSize(2);
        assertThat(userOrders).extracting(Order::getUserId)
                .allMatch(id -> id.equals(1L));
    }
}

AI 理解数据库表结构

关键能力

要让 AI 生成有效的 Repository 测试,AI 必须理解数据库的表结构、字段类型、约束关系和索引设计。

AI 如何获取数据库结构信息

flowchart LR
    subgraph 信息源
        S1[迁移脚本 SQL]
        S2[实体类注解]
        S3[ER 图/文档]
    end
    
    subgraph AI 理解
        A1[表结构]
        A2[字段映射]
        A3[关联关系]
        A4[约束条件]
    end
    
    subgraph 测试生成
        T1[CRUD 测试]
        T2[关联查询测试]
        T3[唯一约束测试]
        T4[索引性能测试]
    end
    
    S1 --> A1
    S2 --> A2
    S3 --> A3
    S1 --> A4
    
    A1 --> T1
    A2 --> T1
    A3 --> T2
    A4 --> T3

提示词中提供的数据库信息

-- 数据库表结构(供 AI 参考)
CREATE TABLE orders (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id     BIGINT       NOT NULL,
    product_id  BIGINT       NOT NULL,
    quantity    INT          NOT NULL CHECK (quantity > 0),
    status      VARCHAR(20)  NOT NULL DEFAULT 'CREATED',
    created_at  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id),
    CONSTRAINT fk_product FOREIGN KEY (product_id) REFERENCES products(id)
);

接口层面验证:Controller 端点全部可用无遗漏

重要原则

每个 Controller 端点都必须有对应的测试。没有测试的端点等于没有验证,等于可能在生产环境中静默失败。

端点覆盖率检查清单

检查项验证方式示例
正常请求状态码 200/201POST /api/orders 创建成功
参数校验失败状态码 400缺少必填字段
资源不存在状态码 404订单 ID 不存在
权限不足状态码 403无权限访问
未认证状态码 401未提供 Token
服务异常状态码 500内部服务崩溃
请求体格式错误状态码 400JSON 解析失败

本课小结

  • AI 按分层架构自动生成测试:Controller → Service → Repository
  • Controller 测试验证端点可用性和 HTTP 行为
  • Service 测试验证业务逻辑和异常处理
  • Repository 测试验证 SQL 和数据映射
  • AI 需要通过迁移脚本/实体类理解数据库结构
  • 端点覆盖率是接口层面验证的核心指标

下一步:第49课:TDD + AI 编程手法总结——全景总结与对比。