自动化测试策略
AI 编程时代的测试策略要解决两个问题:第一,证明功能真的符合验收标准;第二,约束 AI 生成代码不要悄悄引入回归。测试不是最后补的文档,而是 AI 任务的一部分。
测试金字塔
text
E2E tests 少量,覆盖关键用户路径
Integration tests 适量,覆盖模块和 API 交互
Unit tests 大量,覆盖纯逻辑和边界条件建议比例:
| 层级 | 比例 | 适合 AI 生成吗 | 说明 |
|---|---|---|---|
| Unit | 60%-70% | 很适合 | 输入输出明确,容易审查 |
| Integration | 20%-30% | 适合 | 需要理解模块边界 |
| E2E | 5%-10% | 谨慎 | 容易受 UI 变化影响 |
docs/project/test-strategy.md 模板
markdown
# Test Strategy
## Scope
This strategy covers the AI project delivery dashboard v1.
## Test Goals
- Verify task creation, update, delete, and filtering.
- Verify dashboard summary counts.
- Verify localStorage persistence.
- Verify empty, loading, and error-like states where applicable.
## Unit Tests
Target:
- Task summary calculation.
- Task filtering.
- Task validation.
- localStorage serialization helpers.
Command:
`npm run test`
## Integration Tests
Target:
- Task form updates task list.
- Status change updates dashboard cards.
- Reset action restores seed data.
Command:
`npm run test`
## E2E Smoke Test
Target:
- Open dashboard.
- Create a task.
- Mark it blocked.
- Confirm blocked count updates.
- Refresh page and confirm data remains.
Command:
`npm run test:e2e`
## Quality Gate
Before a change is complete:
- Unit tests pass.
- Build passes.
- At least one browser smoke path is manually verified if E2E is unavailable.
- Known untested areas are listed in the final report.单元测试示例
typescript
import { describe, expect, it } from 'vitest'
import { summarizeTasks } from './summarizeTasks'
describe('summarizeTasks', () => {
it('counts tasks by status', () => {
const result = summarizeTasks([
{ id: '1', status: 'todo' },
{ id: '2', status: 'in_progress' },
{ id: '3', status: 'blocked' },
{ id: '4', status: 'done' },
{ id: '5', status: 'blocked' },
])
expect(result.total).toBe(5)
expect(result.todo).toBe(1)
expect(result.inProgress).toBe(1)
expect(result.blocked).toBe(2)
expect(result.done).toBe(1)
})
})集成测试示例
typescript
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { TaskDashboard } from './TaskDashboard'
it('creates a task and updates the dashboard count', async () => {
render(<TaskDashboard />)
await userEvent.type(screen.getByLabelText('Task title'), 'Prepare release checklist')
await userEvent.selectOptions(screen.getByLabelText('Priority'), 'high')
await userEvent.click(screen.getByRole('button', { name: 'Create task' }))
expect(screen.getByText('Prepare release checklist')).toBeInTheDocument()
expect(screen.getByTestId('total-count')).toHaveTextContent('1')
})E2E Smoke 示例
typescript
import { expect, test } from '@playwright/test'
test('task delivery smoke flow', async ({ page }) => {
await page.goto('/')
await page.getByLabel('Task title').fill('Fix login edge case')
await page.getByLabel('Owner').fill('Dev A')
await page.getByLabel('Priority').selectOption('high')
await page.getByRole('button', { name: 'Create task' }).click()
await page.getByText('Fix login edge case').click()
await page.getByLabel('Status').selectOption('blocked')
await page.getByLabel('Blocker reason').fill('Need API contract confirmation')
await page.getByRole('button', { name: 'Save task' }).click()
await expect(page.getByTestId('blocked-count')).toHaveText('1')
})给 Claude Code / Codex 的测试提示
text
请先阅读 docs/project/test-strategy.md。
为当前变更补最小必要测试。
不要为了通过测试而降低验收标准。
完成后运行测试和构建,并说明未覆盖风险。测试完成标准
- 测试覆盖核心业务逻辑,而不是只覆盖渲染存在。
- 至少有一个失败用例能证明边界被处理。
- 测试名称描述用户行为或业务规则。
- 测试不依赖执行顺序。
- 测试数据不污染真实数据。
常见反模式
| 反模式 | 问题 | 替代做法 |
|---|---|---|
| 只测快照 | 难以证明行为 | 测用户行为和状态变化 |
| E2E 过多 | 慢且脆弱 | 关键路径保留 E2E,逻辑下沉到单元测试 |
| Mock 一切 | 集成风险被隐藏 | 对关键模块做少量集成测试 |
| 没有负向用例 | 边界缺陷遗漏 | 加非法输入、空状态、权限失败 |
| AI 生成后不审查 | 断言可能无意义 | 人类检查断言是否对应验收 |
课堂练习
- 让 AI 读取
acceptance.md和test-strategy.md。 - 要求 AI 先列出测试清单,不写代码。
- 选择 2 个单元测试和 1 个 smoke 测试实现。
- 故意改坏一个业务规则,确认测试会失败。
- 恢复代码并运行完整验证。