AI生成单元测试案例

wen java案例 2

本文目录导读:

AI生成单元测试案例

  1. 核心思路:控制AI生成的质量
  2. 场景一:基础函数测试 (无外部依赖)
  3. 场景二:涉及Mock外部依赖 (数据库/API)
  4. 场景三:边界条件与状态机
  5. 高级技巧:让AI帮你生成“测试数据”
  6. 最佳实践清单

这是一份关于 如何有效利用AI生成单元测试案例 的完整指南,我将涵盖 通用提示词技巧代码示例(以Java + JUnit为例,但思路适用于Python unittest、Jest等),以及 不同场景下的最佳实践

核心思路:控制AI生成的质量

AI生成的单元测试质量取决于 上下文指令的精确度,不要只说“给我写个测试”,而是提供:

  1. 代码本身 (最好直接粘贴函数/类)。
  2. 被测功能描述 (边界条件、异常情况)。
  3. 测试框架 (JUnit 5, pytest, Go testing等)。
  4. 风格偏好 (Given-When-Then模式, Mockito模拟等)。

基础函数测试 (无外部依赖)

这是AI最擅长的情况,假设我们有如下Java函数:

public class Calculator {
    public int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("除数不能为0");
        }
        return a / b;
    }
}

AI提示词示例 (ChatGPT, Copilot, Cursor):

提示: 请为以下Java方法生成JUnit 5单元测试,使用 @DisplayName@ParameterizedTest 测试正常情况,并单独测试除以0的异常情况,遵循Given-When-Then模式。

public class Calculator {
    public int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("除数不能为0");
        }
        return a / b;
    }
}

AI生成结果:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("计算器除法单元测试")
class CalculatorTest {
    private final Calculator calculator = new Calculator();
    @ParameterizedTest(name = "{0} / {1} = {2}")
    @CsvSource({
        "10, 2, 5",
        "9, 3, 3",
        "0, 5, 0",
        "-6, 2, -3"
    })
    @DisplayName("测试正常除法")
    void testDivide(int a, int b, int expected) {
        // Given - 参数已由CsvSource提供
        // When
        int result = calculator.divide(a, b);
        // Then
        assertEquals(expected, result, "除法计算结果错误");
    }
    @Test
    @DisplayName("除数为0时应抛出异常")
    void testDivideByZero() {
        // Given
        int a = 5;
        int b = 0;
        // When & Then
        IllegalArgumentException exception = assertThrows(
            IllegalArgumentException.class,
            () -> calculator.divide(a, b)
        );
        assertEquals("除数不能为0", exception.getMessage());
    }
}

涉及Mock外部依赖 (数据库/API)

这是AI生成测试最有价值的场景,因为它能帮你创建隔离的测试。

// 假设Service依赖一个UserRepository
public class UserService {
    private final UserRepository userRepository;
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    public User getUserById(Long id) {
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("无效ID");
        }
        return userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("用户未找到"));
    }
}

AI提示词示例:

提示: 为下面的 UserService.getUserById 方法生成JUnit 5 + Mockito单元测试,请覆盖:

  1. 用户存在且返回正常。
  2. 用户不存在(抛出异常)。
  3. ID为空(抛出非法参数异常)。
  4. ID为负数。 使用 @ExtendWith(MockitoExtension.class)@Mock@InjectMocks 注解。

AI生成结果:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("用户服务单元测试")
class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    @InjectMocks
    private UserService userService;
    @Test
    @DisplayName("根据ID获取用户 - 成功")
    void testGetUserById_Success() {
        // Given
        Long userId = 1L;
        User mockUser = new User(1L, "Alice");
        when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser));
        // When
        User result = userService.getUserById(userId);
        // Then
        assertNotNull(result);
        assertEquals("Alice", result.getName());
        verify(userRepository, times(1)).findById(userId);
    }
    @Test
    @DisplayName("根据ID获取用户 - 用户不存在")
    void testGetUserById_NotFound() {
        // Given
        Long userId = 999L;
        when(userRepository.findById(userId)).thenReturn(Optional.empty());
        // When & Then
        RuntimeException exception = assertThrows(RuntimeException.class,
                () -> userService.getUserById(userId));
        assertEquals("用户未找到", exception.getMessage());
    }
    @Test
    @DisplayName("传入null ID应抛出异常")
    void testGetUserById_NullId() {
        // When & Then
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
                () -> userService.getUserById(null));
        assertEquals("无效ID", exception.getMessage());
        // 确保Repository没有被调用
        verifyNoInteractions(userRepository);
    }
    @Test
    @DisplayName("传入负数ID应抛出异常")
    void testGetUserById_NegativeId() {
        // When & Then
        assertThrows(IllegalArgumentException.class,
                () -> userService.getUserById(-5L));
        verifyNoInteractions(userRepository);
    }
}

边界条件与状态机

如果被测逻辑复杂(如订单状态流转),AI可以帮助你系统地覆盖所有路径。

AI提示词 (Python + pytest 风格):

提示: 你是一个测试工程师,请为下面的Python函数编写 pytest 测试,使用 parametrize 装饰器覆盖所有边界情况:空列表、单元素、已排序、倒序。

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + middle + quick_sort(right)

AI生成结果:

import pytest
@pytest.mark.parametrize("input_list, expected", [
    ([], []),                     # 空列表
    ([1], [1]),                   # 单元素
    ([3, 1, 2], [1, 2, 3]),      # 正常排序
    ([1, 2, 3], [1, 2, 3]),      # 已排序
    ([3, 2, 1], [1, 2, 3]),      # 倒序
    ([5, 5, 5, 5], [5, 5, 5, 5]),# 所有元素相同
    ([1, 0, -1, 2], [-1, 0, 1, 2]), # 包含负数
])
def test_quick_sort(input_list, expected):
    assert quick_sort(input_list) == expected

高级技巧:让AI帮你生成“测试数据”

有时候写测试本身不难,难的是构造复杂的数据,AI非常擅长这个。

提示词:

请生成一个 Customer 对象的JSON数组,用于单元测试,要求:

  • 包含3个客户。
  • 1个VIP客户(vipLevel = 5),lastPurchaseDate 是昨天。
  • 1个新客户(注册日期为今天,无购买记录)。
  • 1个休眠客户(最近购买在3年前)。
  • 所有ID唯一且为正整数。

然后你可以将这个JSON直接粘贴到测试代码中作为测试数据,非常高效。

最佳实践清单

  1. 提供代码: 不一定要全类,但至少要提供被测方法的签名核心逻辑
  2. 指定框架: “用JUnit 5”、“用Jest”、“用pytest”。
  3. 指定模式: “用Given-When-Then注释”、“用Mockito的BDDMockito”。
  4. 覆盖类型:
    • 快乐路径(Happy Path)
    • 异常路径(Exception Path)
    • 边界值(Edge Case: 0, null, empty, max value)
  5. 验证行为: 不仅要 assertEquals,对于Mock对象,要加 verify(确认调用了/没调用模拟的方法)。
  6. 迭代优化: 如果第一次生成的测试不好,可以追加指令:“测试覆盖率不够,请增加对参数为null的测试” 或 “不要用PowerMock,请用纯Mockito”。

如果你有具体的函数或代码片段需要生成测试,请直接发给我,我给你生成对应的案例。

抱歉,评论功能暂时关闭!