第 6 章:测试驱动开发 — 红灯绿灯重构
💡 进群学习加 wx: agentupdate
(申请发送: agentupdate)
(申请发送: agentupdate)
你将学到什么
- 什么是 TDD(测试驱动开发)以及为什么 AI 时代它更重要
- 用 superpowers 的
/test-driven-development工作流 - 红灯 → 绿灯 → 重构的循环
- 为 NoteFlow 补充边界测试用例
6.1 为什么 AI 时代测试更重要
有个反直觉的事实:AI 写代码越快,测试就越重要。
原因:
| 场景 | 没有测试 | 有测试 |
|---|---|---|
| AI 写了一个函数 | 看起来对,但不知道边界情况对不对 | 跑一下测试就知道 |
| 改了一个功能 | 不知道有没有改坏其他功能 | 全量测试跑一遍,3 秒出结果 |
| AI 重构了代码 | 不知道功能是否保持一致 | 测试通过 = 功能一致 |
测试是 AI 编码的安全网。 没有 safety net,你不敢让 AI 大规模改动代码。
6.2 TDD 三步循环
flowchart LR
A["🔴 红灯
写一个失败的测试
(描述你想要的行为)"] --> B["🟢 绿灯
写最少的代码
让测试通过"]
B --> C["♻️ 重构
改善代码结构
测试仍然通过"]
C --> A
style A fill:#fecaca,stroke:#dc2626
style B fill:#dcfce7,stroke:#16a34a
style C fill:#dbeafe,stroke:#2563eb红灯 — 先写测试,描述你想要的功能。这时候代码还没写,测试一定失败。
绿灯 — 写最少的代码让测试通过。不多不少,刚刚好。
重构 — 测试通过了,现在可以放心地改善代码。改完再跑一次测试,确认没改坏。
6.3 /test-driven-development 工作流
命令语法
/test-driven-development [功能描述]
实战:为 NoteFlow 补充搜索测试
第 5 章用 subagent 实现了核心功能,但搜索的边界情况测试不够。我们来补充。
/test-driven-development
为 NoteFlow 的搜索功能补充边界测试:
- 搜索空字符串应该返回所有笔记
- 搜索大小写应该不敏感
- 搜索结果应该高亮匹配文本
- 搜索特殊字符不应报错
- 搜索中文内容应正常工作
TDD 过程演示:
红灯:写测试
// src/hooks/useSearch.test.js
import { renderHook } from '@testing-library/react';
import { useSearch } from './useSearch';
describe('useSearch', () => {
const notes = [
{ id: '1', title: '工作计划', content: '本周任务清单' },
{ id: '2', title: '购物清单', content: '牛奶、面包' },
{ id: '3', title: '学习笔记', content: 'React Hooks 总结' },
];
test('空搜索返回所有笔记', () => {
const { result } = renderHook(() => useSearch(notes, ''));
expect(result.current).toHaveLength(3);
});
test('搜索不区分大小写', () => {
const { result } = renderHook(() => useSearch(notes, 'react'));
expect(result.current).toHaveLength(1);
expect(result.current[0].id).toBe('3');
});
test('搜索特殊字符不报错', () => {
const { result } = renderHook(() => useSearch(notes, '[test]'));
expect(result.current).toHaveLength(0);
});
test('搜索中文内容', () => {
const { result } = renderHook(() => useSearch(notes, '清单'));
expect(result.current).toHaveLength(2);
});
});
运行测试:
npm test -- src/hooks/useSearch.test.js
预期结果:红灯(函数还没实现)
绿灯:写最少的实现
// src/hooks/useSearch.js
import { useMemo } from 'react';
export function useSearch(notes, query) {
return useMemo(() => {
if (!query.trim()) return notes;
const lowerQuery = query.toLowerCase();
return notes.filter(note =>
note.title.toLowerCase().includes(lowerQuery) ||
note.content.toLowerCase().includes(lowerQuery)
);
}, [notes, query]);
}
运行测试:
npm test -- src/hooks/useSearch.test.js
预期结果:绿灯(4 tests passed)
重构:改善代码
当前实现可以工作,但有两个可以改进的地方:
- 搜索逻辑可以提取成独立函数,方便测试
- 可以加防抖,避免每次输入都触发搜索
// src/utils/search.js
export function filterNotes(notes, query) {
if (!query.trim()) return notes;
const lowerQuery = query.toLowerCase();
return notes.filter(note =>
note.title.toLowerCase().includes(lowerQuery) ||
note.content.toLowerCase().includes(lowerQuery)
);
}
// src/hooks/useSearch.js
import { useMemo } from 'react';
import { filterNotes } from '../utils/search';
export function useSearch(notes, query) {
return useMemo(() => filterNotes(notes, query), [notes, query]);
}
运行测试:
npm test
绿灯(所有测试通过,重构没有改坏功能)。
6.4 边界测试清单
完整的 NoteFlow 测试应该覆盖以下边界情况:
mindmap
root((测试覆盖))
数据操作
创建空标题笔记
创建超长内容笔记
删除不存在的笔记
更新不存在的笔记
localStorage 已满时的处理
搜索
空字符串搜索
大小写不敏感
特殊字符不报错
中文搜索
无结果时的空状态
分类
给笔记添加多个标签
移除标签
按标签筛选无结果
空标签列表
UI 交互
笔记列表为空时的引导
删除确认弹窗
搜索输入框清空
标签筛选切换6.5 测试覆盖率目标
用 vitest 的覆盖率工具:
npm test -- --coverage
覆盖率等级:
| 等级 | 语句覆盖 | 适合场景 |
|---|---|---|
| 基础 | > 60% | 快速原型 |
| 合格 | > 80% | 正式项目(推荐) |
| 优秀 | > 95% | 关键业务逻辑 |
对于 NoteFlow:
- 工具函数(storage.js、search.js):目标 95%
- 自定义 Hooks(useNotes.js、useSearch.js):目标 90%
- UI 组件:目标 70%(UI 测试价值较低,不用追求高覆盖)
动手做
- 运行
/test-driven-development,为搜索功能补充测试 - 按照红灯 → 绿灯 → 重构的循环实现
- 运行
npm test -- --coverage查看覆盖率 - 检查覆盖率报告,找出未覆盖的边界情况
- 补充缺失的测试
本章小结
| 概念 | 说明 |
|---|---|
| 红灯 | 先写失败的测试(描述期望行为) |
| 绿灯 | 写最少代码让测试通过 |
| 重构 | 改善代码结构,测试仍然通过 |
| 边界测试 | 空值、特殊字符、异常输入等极端情况 |
| 覆盖率 | 量化测试完整性,80% 是合格线 |
核心原则: 测试先行,不是测试后补。先写测试能帮你想清楚"这个函数应该做什么",再写代码就是填空题而不是开放题。