AI编程07:测试生成
从单测、集成测试、边界用例到回归清单,理解如何使用AI自动生成高质量测试用例。
测试生成是 AI 最有价值的应用场景之一。AI 可以根据代码自动生成单元测试、集成测试、边界用例和回归清单,大幅提升测试覆盖率和开发效率。
测试生成的价值
传统测试的问题
| 问题 | 表现 | 影响 |
|---|---|---|
| 手动编写慢 | 需要手动编写大量测试代码 | 效率低 |
| 覆盖率低 | 容易遗漏边界条件 | 质量差 |
| 维护成本高 | 代码变更后需要更新测试 | 维护难 |
| 不全面 | 只测试正常路径,忽略异常路径 | 风险高 |
AI 测试生成的优势
| 能力 | 说明 | 效果 |
|---|---|---|
| 快速生成 | 自动生成测试代码 | 节省时间 |
| 高覆盖率 | 覆盖正常和异常路径 | 质量高 |
| 智能边界 | 自动识别边界条件 | 全面性好 |
| 自适应 | 代码变更后自动更新测试 | 维护简单 |
测试类型与生成策略
单元测试
单元测试测试单个方法或类的功能:
代码:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return a / b;
}
}
生成的单元测试:
@Test
void testAdd_Normal() {
assertEquals(5, calculator.add(2, 3));
}
@Test
void testAdd_Negative() {
assertEquals(-1, calculator.add(2, -3));
}
@Test
void testAdd_Zero() {
assertEquals(0, calculator.add(0, 0));
}
@Test
void testDivide_Normal() {
assertEquals(2, calculator.divide(6, 3));
}
@Test
void testDivide_ZeroDivisor() {
assertThrows(IllegalArgumentException.class, () ->
calculator.divide(6, 0));
}
@Test
void testDivide_Negative() {
assertEquals(-2, calculator.divide(-6, 3));
}
集成测试
集成测试测试多个模块之间的交互:
代码:
public class OrderService {
private UserRepository userRepository;
private ProductRepository productRepository;
private OrderRepository orderRepository;
public Order createOrder(Long userId, List<Long> productIds) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException());
List<Product> products = productRepository.findAllById(productIds);
if (products.size() != productIds.size()) {
throw new ProductNotFoundException();
}
Order order = new Order(user, products);
return orderRepository.save(order);
}
}
生成的集成测试:
@Test
void testCreateOrder_Success() {
// 准备测试数据
User user = new User("test@example.com");
user = userRepository.save(user);
Product product = new Product("Test Product", 100);
product = productRepository.save(product);
// 执行测试
Order order = orderService.createOrder(user.getId(),
Collections.singletonList(product.getId()));
// 验证结果
assertNotNull(order);
assertEquals(user.getId(), order.getUser().getId());
assertEquals(1, order.getProducts().size());
}
@Test
void testCreateOrder_UserNotFound() {
assertThrows(UserNotFoundException.class, () ->
orderService.createOrder(999L, Collections.emptyList()));
}
@Test
void testCreateOrder_ProductNotFound() {
User user = userRepository.save(new User("test@example.com"));
assertThrows(ProductNotFoundException.class, () ->
orderService.createOrder(user.getId(),
Collections.singletonList(999L)));
}
边界用例测试
边界用例测试测试极端和边缘情况:
| 边界类型 | 示例 | 测试用例 |
|---|---|---|
| 空值 | 参数为 null | testDivide_NullDivisor |
| 边界值 | 最大值、最小值 | testAdd_MaxValue |
| 空集合 | 空列表 | testCreateOrder_EmptyProductList |
| 超大集合 | 大量数据 | testCreateOrder_LargeProductList |
| 特殊字符 | 特殊符号输入 | testValidateEmail_SpecialCharacters |
边界测试示例:
代码:
public String validateEmail(String email) {
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("邮箱不能为空");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("邮箱格式不正确");
}
return email;
}
生成的边界测试:
@Test
void testValidateEmail_Null() {
assertThrows(IllegalArgumentException.class, () ->
validator.validateEmail(null));
}
@Test
void testValidateEmail_Empty() {
assertThrows(IllegalArgumentException.class, () ->
validator.validateEmail(""));
}
@Test
void testValidateEmail_NoAtSymbol() {
assertThrows(IllegalArgumentException.class, () ->
validator.validateEmail("test.example.com"));
}
@Test
void testValidateEmail_OnlyAtSymbol() {
assertThrows(IllegalArgumentException.class, () ->
validator.validateEmail("@"));
}
@Test
void testValidateEmail_Valid() {
assertEquals("test@example.com",
validator.validateEmail("test@example.com"));
}
回归测试清单
回归测试清单确保代码变更不会破坏现有功能:
AI:为 OrderService 生成回归测试清单
回归测试清单:
1. 用户注册后能正常登录
2. 用户登录后能创建订单
3. 创建订单时库存检查正常
4. 创建订单时用户状态检查正常
5. 订单创建后库存正确扣减
6. 订单创建后缓存正确更新
7. 订单创建后消息正确发送
8. 异常情况下订单状态正确处理
9. 并发创建订单时数据一致性
10. 边界条件下订单创建正常
AI 测试生成的技巧
如何让测试更准确
| 技巧 | 说明 | 示例 |
|---|---|---|
| 提供代码 | 提供被测试的代码 | 粘贴代码片段 |
| 提供上下文 | 提供相关依赖 | 提供 Repository 接口 |
| 指定测试框架 | 指定使用的测试框架 | ”使用 JUnit 5” |
| 指定断言库 | 指定使用的断言库 | ”使用 AssertJ” |
| 提供测试数据 | 提供测试数据 | 提供测试实体类 |
如何优化测试质量
| 技巧 | 说明 | 示例 |
|---|---|---|
| 要求覆盖边界 | 要求覆盖边界条件 | ”覆盖空值、边界值” |
| 要求覆盖异常 | 要求覆盖异常情况 | ”覆盖所有异常分支” |
| 要求测试隔离 | 要求测试相互隔离 | ”每个测试独立” |
| 要求可读命名 | 要求测试命名清晰 | ”testMethodName_Scenario” |
如何验证测试质量
| 验证方式 | 说明 | 示例 |
|---|---|---|
| 运行测试 | 运行测试验证 | mvn test |
| 检查覆盖率 | 检查代码覆盖率 | JaCoCo 报告 |
| 人工审查 | 人工审查测试代码 | 检查逻辑正确性 |
| 边界验证 | 验证边界条件 | 手动测试边界 |
AI 测试生成的常见问题
问题1:测试覆盖不全
表现:AI 生成的测试没有覆盖所有分支
解决方案:
- 使用代码覆盖率工具检查
- 要求 AI 覆盖特定分支
- 手动补充遗漏的测试
问题2:测试有错误
表现:AI 生成的测试代码有语法或逻辑错误
解决方案:
- 运行测试验证
- 人工审查测试代码
- 提供更多上下文
问题3:测试依赖复杂
表现:测试需要复杂的依赖设置
解决方案:
- 使用 Mock 对象
- 提供测试配置
- 使用测试容器
问题4:测试命名不清晰
表现:测试方法命名不清晰
解决方案:
- 指定命名规则
- 要求遵循约定
- 手动重命名
AI 测试生成的最佳实践
测试生成流程
1. 编写业务代码
2. 使用 AI 生成测试代码
3. 运行测试验证
4. 检查代码覆盖率
5. 补充遗漏的测试
6. 审查测试代码
7. 提交测试代码
测试维护流程
1. 代码变更后
2. 使用 AI 更新相关测试
3. 运行回归测试
4. 修复失败的测试
5. 更新测试文档
测试质量保障
1. 代码覆盖率目标:> 80%
2. 关键路径覆盖率:100%
3. 边界条件覆盖率:100%
4. 异常路径覆盖率:100%
5. 定期运行回归测试
6. 使用静态分析工具
项目判断清单
- 需要快速生成测试 → 使用 AI 测试生成
- 需要提高测试覆盖率 → 使用 AI 覆盖边界和异常
- 需要维护测试 → 使用 AI 更新测试
- 需要回归测试 → 使用 AI 生成回归清单
- 测试覆盖不全 → 使用代码覆盖率工具检查
- 测试有错误 → 运行测试并人工审查
- 测试依赖复杂 → 使用 Mock 和测试容器
- 需要长期维护 → 建立测试文档和规范