如何让AI写测试用例
测试是代码质量的保险,而 AI 可以帮你快速写好这份保险。
在实际开发中,写测试往往是「知道该做但总是拖延」的事情。它不产生业务功能,却能在关键时刻救你一命——当你改了一行代码导致三个页面同时崩溃时,测试会立刻告诉你问题出在哪里。问题是,写测试确实需要时间,而且很多开发者对测试框架、Mock 策略、断言写法并不熟悉。
AI 编程工具恰好可以弥补这个缺口。无论是 Copilot、Claude Code 还是 Cursor,它们都能根据源代码快速生成结构合理的测试用例。但这不意味着你可以完全跳过思考——你需要理解测试类型、选对框架、判断 AI 生成的测试是否有效,并在覆盖率数字之外关注真正的质量。
本章将系统讲解如何让 AI 帮你写测试:从测试类型的选择到框架对比,从 Prompt 设计到覆盖率分析,最后给出一套可复用的检查清单。
测试类型详解
在让 AI 生成测试之前,你需要明确三件事:你要测什么、测到什么粒度、在什么环境下验证。这对应着三种测试类型。
单元测试(Unit Test)
单元测试验证的是最小可测试单元——通常是一个函数、一个方法或一个类。它的核心特征是「隔离」:不依赖数据库、不请求网络、不涉及文件系统。
// 被测函数
function calculateDiscount(price: number, memberLevel: string): number {
if (price < 0) throw new Error('价格不能为负数')
const rates: Record<string, number> = { gold: 0.8, silver: 0.9, normal: 1.0 }
return price * (rates[memberLevel] ?? 1.0)
}
// AI 生成的单元测试
describe('calculateDiscount', () => {
it('金卡会员享受 8 折优惠', () => {
expect(calculateDiscount(100, 'gold')).toBe(80)
})
it('传入负数价格时抛出错误', () => {
expect(() => calculateDiscount(-10, 'gold')).toThrow('价格不能为负数')
})
it('未知会员等级按原价计算', () => {
expect(calculateDiscount(100, 'unknown')).toBe(100)
})
})单元测试的优势在于速度快、定位准。一个项目如果有 500 个单元测试,跑完通常只需要几秒。当测试失败时,你几乎可以确定问题就在那个函数里。AI 生成单元测试的效率也最高——函数签名和类型定义已经提供了足够的上下文。
集成测试(Integration Test)
集成测试验证的是多个模块协作时的行为。典型场景包括:API 路由能否正确处理请求并返回预期响应、数据库操作能否正确读写、多个服务之间的调用链是否通畅。
// 集成测试示例:API 路由
import { app } from '../src/app'
describe('POST /api/orders', () => {
it('创建订单后返回订单 ID 并写入数据库', async () => {
const res = await app.request('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ productId: 'p1', quantity: 2 }),
})
expect(res.status).toBe(201)
const data = await res.json()
expect(data.orderId).toBeDefined()
// 验证数据库确实写入了记录
const record = await db.order.findUnique({ where: { id: data.orderId } })
expect(record).not.toBeNull()
expect(record?.quantity).toBe(2)
})
})集成测试的编写复杂度明显更高:你需要准备测试数据、搭建测试环境、处理副作用。但它的价值也更大——很多 bug 不在单个函数里,而在模块之间的接口上。AI 生成集成测试时,需要你提供数据模型、路由定义等上下文信息,否则它只能猜测你的 API 设计。
E2E 测试(End-to-End Test)
E2E 测试从用户视角出发,模拟真实用户在浏览器中的操作流程。它验证的是完整的功能链路:从打开页面、点击按钮、填写表单到最终看到预期结果。
// Playwright E2E 测试
import { test, expect } from '@playwright/test'
test('用户可以完成下单流程', async ({ page }) => {
await page.goto('/products')
await page.getByRole('link', { name: 'AI 出海指南' }).click()
await page.getByRole('button', { name: '立即购买' }).click()
await page.getByLabel('数量').fill('2')
await page.getByRole('button', { name: '确认下单' }).click()
await expect(page.getByText('订单创建成功')).toBeVisible()
await expect(page.getByTestId('order-id')).not.toBeEmpty()
})E2E 测试运行最慢(涉及真实浏览器),维护成本最高(UI 变动会破坏定位器),但它提供的信心也最强——如果 E2E 测试通过,说明这个功能对用户来说是可用的。
| 维度 | 单元测试 | 集成测试 | E2E 测试 |
|---|---|---|---|
| 测试对象 | 单个函数/方法 | 模块间协作 | 完整用户流程 |
| 运行速度 | 毫秒级 | 百毫秒到秒级 | 秒到分钟级 |
| 依赖隔离 | 完全隔离 | 部分依赖真实环境 | 真实浏览器 + 后端 |
| 维护成本 | 低 | 中 | 高 |
| 反馈速度 | 极快 | 较快 | 慢 |
| 信心程度 | 验证逻辑正确性 | 验证接口契约 | 验证用户可感知行为 |
| AI 生成难度 | 低 | 中 | 高 |
| 典型占比(测试金字塔) | 60%-70% | 20%-30% | 5%-10% |
这张表遵循的是经典的「测试金字塔」原则:底层单元测试最多,顶层 E2E 测试最少。不要反过来——如果 E2E 测试比单元测试还多,你的 CI 会慢到让人崩溃。
测试框架选择
选对框架是写测试的前提。2026 年的 JavaScript/TypeScript 生态中,主流选择如下:
| 框架 | 定位 | 速度 | TypeScript 支持 | ESM 支持 | 适用场景 |
|---|---|---|---|---|---|
| Vitest | 全功能测试框架 | 极快(Vite 驱动) | 原生支持 | 原生支持 | 新项目首选,Vite 项目天然适配 |
| Jest | 全功能测试框架 | 较快 | 需配置 ts-jest 或 babel | 支持但不完善 | 大型项目、React 项目、遗留项目 |
| Playwright | E2E 测试框架 | — | 原生支持 | 原生支持 | 跨浏览器 E2E 测试 |
| Cypress | E2E 测试框架 | — | 原生支持 | 支持 | 需要实时调试的 E2E 测试 |
对于新项目,Vitest 在 2026 年已经是事实上的首选。它的 Watch 模式比 Jest 快约 8 倍(400ms vs 2-3s),ESM 支持是一等公民,与 Vite 生态无缝衔接。Jest 依然在 React Native 和超大规模测试套件(80,000+ 测试)中有优势,但新项目选 Vitest 几乎没有争议。
E2E 测试方面,Playwright 在 2025-2026 年逐步超越了 Cypress。它支持 Chromium、Firefox 和 WebKit 三大引擎,内置自动等待机制,并且已经推出了 MCP(Model Context Protocol)支持——这意味着 AI 可以直接通过 MCP 生成和运行 Playwright 测试。
AI 生成测试用例的流程
让 AI 写测试不是「把代码扔给 AI,然后等结果」这么简单。一个有效的流程需要几步配合:
第一步:提供上下文
AI 不了解你的项目结构、数据模型和业务规则。你需要在 Prompt 中提供足够的上下文:
- 被测代码的完整实现
- 相关的类型定义和数据模型
- 业务规则说明(「金卡会员 8 折,银卡 9 折」)
- 现有的测试文件(让 AI 遵循项目的测试风格)
第二步:明确测试目标
不要只说「帮我写测试」。你需要指定:
- 测试类型:单元测试还是集成测试
- 覆盖范围:正常路径、边界条件、错误处理
- 框架约束:使用 Vitest,使用
describe/it结构 - 命名约定:测试描述用中文还是英文
第三步:审查生成结果
AI 生成的测试可能存在以下问题:
- 断言无效:只检查「不报错」而没有真正的断言
- 覆盖盲区:遗漏了关键的边界条件
- Mock 过度:把不该 Mock 的东西也 Mock 了,导致测试和实现脱节
- 幻觉代码:导入了不存在的模块或 API
第四步:运行并迭代
将生成的测试跑一遍,检查是否全部通过。如果 AI 生成的测试在你还没改代码时就通过了,说明测试可能不够严格——它可能只是在验证「代码能跑」而没有验证「代码正确」。
不同代码类型的测试策略
不同类型的代码需要不同的测试策略。AI 在每种类型上的表现和你需要提供的上下文也不同。
| 代码类型 | 测试重点 | AI 生成难度 | 你需要提供的上下文 | 推荐测试类型 |
|---|---|---|---|---|
| 纯函数 | 输入输出映射、边界值、异常处理 | 低 | 函数签名 + 业务规则 | 单元测试 |
| React 组件 | 渲染逻辑、用户交互、状态变化 | 中 | 组件代码 + 交互需求描述 | 单元测试 + E2E |
| API 路由 | 请求校验、响应格式、错误码、鉴权 | 中 | 路由定义 + 数据模型 + 鉴权逻辑 | 集成测试 |
| 数据库操作 | CRUD 正确性、事务、并发 | 高 | Schema 定义 + 查询逻辑 + 约束 | 集成测试 |
| 复杂业务编排 | 多步骤流程、状态机、重试逻辑 | 高 | 完整业务流程 + 状态转换规则 | 集成测试 + E2E |
纯函数的测试
这是 AI 最擅长的领域。一个有明确输入输出的函数,AI 可以快速覆盖正常路径、边界值和异常情况。你的 Prompt 可以更直接:
请为以下函数生成 Vitest 单元测试,覆盖:
1. 正常输入的预期输出
2. 边界值(空数组、0、极大值)
3. 非法输入的异常处理
函数代码如下:[粘贴代码]
React 组件的测试
组件测试有两种思路:用 Vitest + Testing Library 做单元测试,验证渲染逻辑和交互;或者用 Playwright 做 E2E 测试,验证完整的用户流程。AI 生成组件测试时,你需要告诉它组件的交互预期——不是「这个组件做了什么」,而是「用户使用这个组件时会发生什么」。
API 路由的测试
API 测试的核心是契约验证:给定特定的请求,响应是否满足预期。AI 需要知道你的路由定义、请求参数、响应格式和错误处理逻辑。如果你有 OpenAPI/Swagger 文档,直接提供给 AI 是最好的上下文。
数据库操作的测试
这是 AI 最容易出错的地方。数据库测试涉及真实的 Schema、事务、约束和并发。AI 可能不知道你的唯一索引规则、外键约束或软删除逻辑。你必须提供完整的 Schema 定义,并在生成后仔细检查 SQL 逻辑。
测试覆盖率的意义和误区
测试覆盖率(Code Coverage)是一个常见指标,但它经常被误解。
覆盖率告诉你的是:「有多少代码被测试执行到了」。80% 的行覆盖率意味着 80% 的代码行至少在某个测试中被执行过一次。但它不告诉你的是:「这些测试是否真正验证了代码的正确性」。
一个常见的反例:
function add(a: number, b: number): number {
return a + b
}
it('add 函数不会报错', () => {
add(1, 2)
// 没有断言,但覆盖率会计入这行
})这个测试贡献了 100% 的行覆盖率,但它什么也没验证。AI 生成的测试有时会出现这种「空断言」问题——它执行了代码,但没有检查输出。
覆盖率指标的真正用法是:
- 发现盲区:覆盖率低的文件说明测试不足,需要补充
- 趋势追踪:覆盖率在增长说明测试覆盖在扩大
- CI 门禁:设定最低覆盖率门槛(比如 70%),防止新代码缺少测试
不要追求 100% 覆盖率。边际成本递增——从 70% 到 90% 可能只需要几个测试,但从 90% 到 100% 可能需要大量精力去测试那些罕见的分支。对于业务关键路径(支付、权限、数据一致性),可以要求高覆盖率;对于 UI 组件和工具函数,合理的覆盖率就够了。
| 覆盖率区间 | 含义 | 建议 |
|---|---|---|
| 0%-30% | 测试严重不足 | 优先为核心逻辑补充测试 |
| 30%-60% | 基础覆盖 | 关键路径仍需加强 |
| 60%-80% | 合理水平 | 关注边界条件和错误路径 |
| 80%-95% | 高质量覆盖 | 维护成本需关注 |
| 95%-100% | 过度追求 | 边际效益极低,不建议作为目标 |
案例一:用 AI 为订单折扣模块生成测试
假设你有一个电商 SaaS 平台的折扣计算模块,涉及会员等级、优惠券叠加和最低消费门槛。
Prompt 示例:
请为以下折扣计算函数生成 Vitest 单元测试:
- 金卡会员 8 折,银卡 9 折
- 优惠券可叠加,但最低消费不低于原价的 50%
- 订单金额为 0 时返回 0
- 覆盖正常路径、边界值和异常输入
[粘贴函数代码]
AI 通常会生成 8-12 个测试用例,覆盖主要场景。你需要检查的是:
- 「最低消费 50%」这个约束是否被正确测试了?
- 优惠券叠加后低于 50% 的场景是否被覆盖?
- 空优惠券列表的情况是否考虑了?
这类审查花不了多少时间,但它决定了测试是否真的有用。
案例二:用 AI 为 Playwright E2E 测试生成用户注册流程
E2E 测试的 Prompt 需要更侧重用户行为描述:
请使用 Playwright 生成 E2E 测试,验证用户注册流程:
1. 访问 /register 页面
2. 填写邮箱和密码
3. 点击注册按钮
4. 验证跳转到欢迎页面
5. 验证数据库中存在新用户记录
使用语义化选择器(getByRole、getByLabel),不要使用 CSS 选择器。
测试数据使用 faker 生成。
AI 生成的 E2E 测试需要特别注意:
- 选择器是否稳定(避免依赖动态 class 名)
- 测试数据是否隔离(不与其他测试共享)
- 异步等待是否正确(不依赖固定延时)
- 清理逻辑是否完善(测试后是否需要删除测试数据)
实践检查清单
以下清单涵盖了 AI 辅助测试的核心环节,每次使用前逐项检查:
- 明确测试类型:为每段代码选择了正确的测试粒度(单元/集成/E2E)
- 框架选型合理:新 JS/TS 项目优先考虑 Vitest,E2E 用 Playwright
- 上下文充分:向 AI 提供了源代码、类型定义、业务规则和现有测试
- 目标明确:Prompt 中指定了测试类型、覆盖范围和命名约定
- 断言有效:每个测试都有真正的断言,不只是「不报错」
- 边界覆盖:正常路径、边界值、异常输入均有测试
- Mock 适度:只 Mock 外部依赖(网络、文件系统),不过度 Mock 内部逻辑
- 无幻觉代码:AI 生成的 import 和 API 调用确实存在
- 测试可独立运行:不依赖其他测试的执行顺序或共享状态
- 覆盖率合理:关注盲区而非追求 100%
- CI 集成:测试在 CI 中自动运行,失败时阻断合并
- 定期维护:过时的测试及时更新或删除,避免产生误导
小结
AI 写测试的核心价值不在于「完全替代人工写测试」,而在于大幅降低写测试的启动成本。一个原本需要 30 分钟的测试文件,AI 可以在 1 分钟内生成 80% 的内容,剩下的 20% 需要你审查、补充和修正。
关键在于:你要理解测试类型和框架选择,知道什么代码需要什么测试,能判断 AI 生成的测试是否有效。AI 是你的测试助手,不是你的测试负责人。
测试是代码质量的保险。AI 帮你快速写好保险,但保单是否有效,最终由你负责。
参考资料
- AI Unit Test Generation: A Detailed Guide - testomat.io
- Vitest vs Jest 2026: Performance Benchmarks - SitePoint
- Generating end-to-end tests with AI and Playwright MCP - Checkly
- How to Write an E2E Automated Test Using AI in 2025 - Leanware
- One of the Best Ways to Get Value for AI Coding Tools: Generating Tests - Stack Overflow
- Pragmatic Testing for AI-Generated Code - Dev.to
- AI-Driven Testing Best Practices - Foojay.io
- 5 Best Practices for Playwright E2E Testing - Momentic