第26期 | 用AI写测试与文档 今天你将学会用 AI 自动生成 Vitest React Testing Library 测试用例用 AI 生成 API 文档和组件文档用 AI 写 README 和项目文档理解 AI 生成测试的质量标准——不是「能跑就行」 核心知识为什么测试和文档是最适合交给 AI 的测试和文档是开发者最不想写的两类内容——不是因为它们不重要而是因为它们重复性高、创造性低。内容类型创造性重复性AI 适配度核心业务逻辑高低❌ AI 辅助人主导UI 组件中中⚠️ AI 生成骨架人微调测试用例低高✅ AI 主导人审查文档低高✅ AI 主导人审查测试的核心价值在于覆盖边界情况——这正是 AI 擅长的它不像人类那样容易忽略如果输入是空数组会怎样“如果用户点了两次按钮会怎样”。用 AI 生成测试用例测试生成的 CRISP Prompt[Context] {要测试的组件/函数} 和 {相关的类型定义} [Role] 前端测试工程师 [Instruction] 为 {组件名/函数名} 生成完整的测试用例 [Specification] - 测试框架Vitest React Testing Library - 测试分类正常流程 边界情况 错误处理 - 每个测试用例要有清晰的描述it(should xxx when yyy) - 不要只测渲染成功要测用户行为和组件响应 - Mock 外部依赖API 调用、路由、store - 集中在行为测试不测实现细节 [Proof] 覆盖以下场景{列出你知道的关键场景}实战给 BlogCard 组件生成测试组件代码简化版interface BlogCardProps { title: string; excerpt: string; author: { name: string; avatar: string }; date: string; category: string; onClick: () void; } export function BlogCard({ title, excerpt, author, date, category, onClick }: BlogCardProps) { return ( article onClick{onClick} classNameblog-card rolebutton tabIndex{0} span classNamecategory-badge{category}/span h3{title}/h3 p classNameline-clamp-2{excerpt}/p div classNameauthor-info img src{author.avatar} alt{${author.name}的头像} / span{author.name}/span time{date}/time /div /article ); }AI 生成的测试用例import { describe, it, expect, vi } from vitest; import { render, screen, fireEvent } from testing-library/react; import { BlogCard } from ./BlogCard; const defaultProps { title: React Hooks 深度指南, excerpt: 本文将深入讲解 React Hooks 的核心概念..., author: { name: 张三, avatar: /avatar.jpg }, date: 2026-01-15, category: React, onClick: vi.fn(), }; describe(BlogCard, () { // 正常流程 it(should render title, excerpt, author and category correctly, () { render(BlogCard {...defaultProps} /); expect(screen.getByText(React Hooks 深度指南)).toBeInTheDocument(); expect(screen.getByText(本文将深入讲解 React Hooks 的核心概念...)).toBeInTheDocument(); expect(screen.getByText(张三)).toBeInTheDocument(); expect(screen.getByText(React)).toBeInTheDocument(); }); it(should call onClick when card is clicked, () { const onClick vi.fn(); render(BlogCard {...defaultProps} onClick{onClick} /); fireEvent.click(screen.getByRole(button)); expect(onClick).toHaveBeenCalledTimes(1); }); it(should call onClick when Enter key is pressed, () { const onClick vi.fn(); render(BlogCard {...defaultProps} onClick{onClick} /); fireEvent.keyDown(screen.getByRole(button), { key: Enter }); expect(onClick).toHaveBeenCalledTimes(1); }); // 边界情况 it(should render correctly with very long title, () { render(BlogCard {...defaultProps} title{这是一个超级长的标题.repeat(10)} /); expect(screen.getByRole(button)).toBeInTheDocument(); }); it(should render correctly with empty excerpt, () { render(BlogCard {...defaultProps} excerpt /); expect(screen.getByRole(button)).toBeInTheDocument(); }); it(should render image with correct alt text, () { render(BlogCard {...defaultProps} /); expect(screen.getByAltText(张三的头像)).toBeInTheDocument(); }); // 错误处理 it(should render fallback when avatar image fails to load, () { render(BlogCard {...defaultProps} /); const img screen.getByAltText(张三的头像); fireEvent.error(img); // 如果有 fallback 组件检查它是否出现 }); });审查 AI 生成的测试——我的修改✅ 正常流程覆盖了渲染内容、点击、键盘操作✅ 边界情况有超长标题、空 excerpt❌ 缺少「连续快速点击」的测试——防止 onClick 被触发多次❌ 缺少「category 为空」的边界情况——category-badge 应该隐藏❌ 缺少「时间格式」的验证——date 应显示为2026年1月15日而不是原始字符串追加的测试it(should not trigger onClick multiple times on rapid clicks, () { const onClick vi.fn(); render(BlogCard {...defaultProps} onClick{onClick} /); const card screen.getByRole(button); fireEvent.click(card); fireEvent.click(card); fireEvent.click(card); // 如果有 debounce/throttle应该只触发一次 expect(onClick).toHaveBeenCalledTimes(3); // 无 debounce 时每次都触发 }); it(should not show category badge when category is empty, () { render(BlogCard {...defaultProps} category /); expect(screen.queryByText(React)).not.toBeInTheDocument(); expect(screen.queryByText()).not.toBeInTheDocument(); });测试质量标准不是「能跑就行」AI 生成的测试能跑 ≠ 测试质量好。好的测试需要满足标准含义AI 常见问题行为驱动测用户行为不测实现细节AI 有时会测「state 值是多少」而不是「UI 显示什么」边界覆盖空/null/超长/异常输入AI 通常覆盖 3-5 个边界你补充遗漏的可维护性测试不会因为小改动就全挂AI 有时测了组件内部实现className一改样式就挂独立性每个测试不依赖其他测试AI 通常没问题但多个测试共享 mock 时可能出问题可读性测试名能清楚说明测什么AI 写的 it(‘renders correctly’) 不如 it(‘should show empty state when list is empty’)行为驱动 vs 实现驱动的对比// ❌ 实现驱动——测内部实现 it(should set loading state to true when fetching, () { // 测了内部 state如果改成 React Query 就挂了 expect(component.state().loading).toBe(true); }); // ✅ 行为驱动——测用户看到什么 it(should show loading spinner while fetching data, () { // 测了 UI 表现不管内部怎么实现 expect(screen.getByRole(progressbar)).toBeInTheDocument(); });用 AI 生成文档API 文档自动生成src/services/api.ts src/types/api.d.ts 根据这些 API 接口定义生成 API 文档格式如下 - 接口名称 - 请求方法 URL - 请求参数含类型和说明 - 响应格式含类型和说明 - 错误码列表 - 使用示例包含请求代码和响应示例 使用 Markdown 格式适合放在项目 wiki 中。组件文档自动生成src/components/BlogCard.tsx 为 BlogCard 组件生成文档包含 1. 概述组件用途和核心功能 2. Props 表格每个 prop 的名称、类型、默认值、是否必传、说明 3. 使用示例至少 2 个不同配置的示例 4. 可访问性说明ARIA 属性和键盘操作 5. 依赖说明依赖的其他组件/库README 自动生成package.json src/ README.md如果存在 为这个项目生成 README包含以下章节 1. 项目简介一段话描述 2. 技术栈列表形式 3. 快速开始安装 启动命令 4. 项目结构目录树 简要说明 5. 开发指南环境要求 配置 开发命令 6. 部署说明 7. 贡献指南 语气简洁务实不要过度营销式描述AI 生成文档的审查要点文档类型审查重点常见问题API 文档参数类型是否跟代码一致、示例是否可运行AI 有时把 optional 参数标成 required组件文档Props 表格是否完整、示例是否覆盖核心场景AI 有时漏了新增的 propsREADME安装命令是否正确、目录结构是否最新AI 可能用了过时的命令npm vs pnpm关键原则文档必须跟代码同步。AI 生成文档后每次代码改动都要更新文档。可以把文档更新也加进 .cursorrules## AI协作规则 - 修改组件 Props 时同步更新组件文档 - 新增 API 接口时同步更新 API 文档 - 改动 package.json 后同步更新 README 的安装命令常见误区误区1测试越写越多越好测试的目标是覆盖关键行为不是覆盖所有代码行。一个「搜索为空时显示提示」的测试比三个「断言 className 是否包含某个值」的测试更有价值。误区2AI 生成的测试不用审查AI 生成的测试可能测了实现细节而不是行为、可能遗漏关键边界情况、可能用了不稳定的 mock。必须审查。误区3文档写了就不会过时文档和代码的关系是「代码改动 → 文档更新」。AI 可以帮你快速生成初始文档但维护文档是你的责任。 AI协作实战实战场景为一个 Zustand Store 生成完整测试Store 代码// userStore.tsimport{create}fromzustand;interfaceUser{id:string;name:string;email:string;role:admin|editor|viewer;}interfaceUserState{users:User[];searchQuery:string;selectedRole:string;loading:boolean;error:string|null;fetchUsers:()Promisevoid;setSearchQuery:(query:string)void;setSelectedRole:(role:string)void;getFilteredUsers:()User[];}我给 AI 的 PromptuserStore.ts 为这个 Zustand Store 生成完整的测试用例。 覆盖 - 正常流程fetchUsers 成功返回数据 - 边界情况空搜索、空角色筛选、同时搜索筛选 - 错误处理fetchUsers 失败 - 状态一致性setSearchQuery 后 getFilteredUsers 返回正确结果 测试框架Vitest 注意Zustand store 测试用 act() 包裹状态变更AI 生成的核心测试import{describe,it,expect,vi,beforeEach}fromvitest;import{useUserStore}from./userStore;// Mock fetchconstmockFetchvi.fn();global.fetchmockFetch;describe(userStore,(){beforeEach((){// 重置 store 状态useUserStore.setState({users:[],searchQuery:,selectedRole:,loading:false,error:null,});mockFetch.mockReset();});it(should fetch users successfully,async(){constmockUsers[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},];mockFetch.mockResolvedValueOnce({ok:true,json:()Promise.resolve(mockUsers),});awaituseUserStore.getState().fetchUsers();expect(useUserStore.getState().users).toEqual(mockUsers);expect(useUserStore.getState().loading).toBe(false);expect(useUserStore.getState().error).toBe(null);});it(should handle fetch error,async(){mockFetch.mockResolvedValueOnce({ok:false,status:500});awaituseUserStore.getState().fetchUsers();expect(useUserStore.getState().users).toEqual([]);expect(useUserStore.getState().error).toBe(Failed to fetch users);expect(useUserStore.getState().loading).toBe(false);});it(should filter users by search query,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},],searchQuery:ali,});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(1);expect(filtered[0].name).toBe(Alice);});it(should filter users by role,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},],selectedRole:admin,});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(1);expect(filtered[0].role).toBe(admin);});it(should combine search and role filter,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Alice Bob,email:alice.bobtest.com,role:editor},{id:3,name:Charlie,email:charlietest.com,role:admin},],searchQuery:ali,selectedRole:admin,});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(1);expect(filtered[0].name).toBe(Alice);expect(filtered[0].role).toBe(admin);});it(should return all users when no filter is applied,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},],});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(2);});});审查要点✅ 每个测试有beforeEach重置状态——确保独立性✅ 覆盖了组合筛选搜索 角色——这是容易遗漏的❌ 缺少「搜索大小写不敏感」的测试——如果 getFilteredUsers 做了大小写处理应该有对应测试❌ 缺少「搜索为空字符串时返回全部」的边界测试——空字符串 ! 无搜索学到了什么AI 生成的测试覆盖了大部分场景但组合筛选和边界情况需要你根据业务逻辑补充。测试 Prompt 中的 [Proof] 部分很关键——它告诉 AI 你特别关心的场景。 动手练习练习1简单为一个工具函数生成测试写一个简单的formatDate(date: string): string函数用 AI 生成测试。审查 AI 生成的测试至少补充 2 个边界情况。练习2中等为你的一个 React 组件生成完整测试选你项目中的一个组件用 CRISP 测试 Prompt 生成测试。运行测试确保全部通过。记录你额外补充的测试用例和原因。练习3挑战建立「测试 → 文档 → README」一站式生成流程为你的项目建立以下 AI 工作流新增组件 → 自动生成组件测试 组件文档新增 API → 自动生成 API 测试 API 文档项目改动 → 自动更新 README把这个流程写成 SOP包含每一步的 Prompt 模板。 本期要点测试和文档最适合交给 AI重复性高、创造性低AI 主导 人审查测试质量标准行为驱动 实现驱动覆盖边界 覆盖行数CRISP 测试 Prompt在 [Proof] 中列出你特别关心的场景让 AI 重点覆盖文档必须跟代码同步AI 生成初始文档每次代码改动时用 AI 更新文档审查 AI 测试的 5 个标准行为驱动、边界覆盖、可维护性、独立性、可读性 下期预告下一期我们进入 AI 辅助学习——如何用 AI 加速知识获取看源码、读文档、学新框架。你将建立一套「AI 学习工作流」让学习效率翻倍。如果你没有苹果电脑需要上传ios到APPStore可以访问以下网站iPA上传工具 - IPA解析与AppStore提交
第26期 | 用AI写测试与文档
发布时间:2026/6/26 3:43:43
第26期 | 用AI写测试与文档 今天你将学会用 AI 自动生成 Vitest React Testing Library 测试用例用 AI 生成 API 文档和组件文档用 AI 写 README 和项目文档理解 AI 生成测试的质量标准——不是「能跑就行」 核心知识为什么测试和文档是最适合交给 AI 的测试和文档是开发者最不想写的两类内容——不是因为它们不重要而是因为它们重复性高、创造性低。内容类型创造性重复性AI 适配度核心业务逻辑高低❌ AI 辅助人主导UI 组件中中⚠️ AI 生成骨架人微调测试用例低高✅ AI 主导人审查文档低高✅ AI 主导人审查测试的核心价值在于覆盖边界情况——这正是 AI 擅长的它不像人类那样容易忽略如果输入是空数组会怎样“如果用户点了两次按钮会怎样”。用 AI 生成测试用例测试生成的 CRISP Prompt[Context] {要测试的组件/函数} 和 {相关的类型定义} [Role] 前端测试工程师 [Instruction] 为 {组件名/函数名} 生成完整的测试用例 [Specification] - 测试框架Vitest React Testing Library - 测试分类正常流程 边界情况 错误处理 - 每个测试用例要有清晰的描述it(should xxx when yyy) - 不要只测渲染成功要测用户行为和组件响应 - Mock 外部依赖API 调用、路由、store - 集中在行为测试不测实现细节 [Proof] 覆盖以下场景{列出你知道的关键场景}实战给 BlogCard 组件生成测试组件代码简化版interface BlogCardProps { title: string; excerpt: string; author: { name: string; avatar: string }; date: string; category: string; onClick: () void; } export function BlogCard({ title, excerpt, author, date, category, onClick }: BlogCardProps) { return ( article onClick{onClick} classNameblog-card rolebutton tabIndex{0} span classNamecategory-badge{category}/span h3{title}/h3 p classNameline-clamp-2{excerpt}/p div classNameauthor-info img src{author.avatar} alt{${author.name}的头像} / span{author.name}/span time{date}/time /div /article ); }AI 生成的测试用例import { describe, it, expect, vi } from vitest; import { render, screen, fireEvent } from testing-library/react; import { BlogCard } from ./BlogCard; const defaultProps { title: React Hooks 深度指南, excerpt: 本文将深入讲解 React Hooks 的核心概念..., author: { name: 张三, avatar: /avatar.jpg }, date: 2026-01-15, category: React, onClick: vi.fn(), }; describe(BlogCard, () { // 正常流程 it(should render title, excerpt, author and category correctly, () { render(BlogCard {...defaultProps} /); expect(screen.getByText(React Hooks 深度指南)).toBeInTheDocument(); expect(screen.getByText(本文将深入讲解 React Hooks 的核心概念...)).toBeInTheDocument(); expect(screen.getByText(张三)).toBeInTheDocument(); expect(screen.getByText(React)).toBeInTheDocument(); }); it(should call onClick when card is clicked, () { const onClick vi.fn(); render(BlogCard {...defaultProps} onClick{onClick} /); fireEvent.click(screen.getByRole(button)); expect(onClick).toHaveBeenCalledTimes(1); }); it(should call onClick when Enter key is pressed, () { const onClick vi.fn(); render(BlogCard {...defaultProps} onClick{onClick} /); fireEvent.keyDown(screen.getByRole(button), { key: Enter }); expect(onClick).toHaveBeenCalledTimes(1); }); // 边界情况 it(should render correctly with very long title, () { render(BlogCard {...defaultProps} title{这是一个超级长的标题.repeat(10)} /); expect(screen.getByRole(button)).toBeInTheDocument(); }); it(should render correctly with empty excerpt, () { render(BlogCard {...defaultProps} excerpt /); expect(screen.getByRole(button)).toBeInTheDocument(); }); it(should render image with correct alt text, () { render(BlogCard {...defaultProps} /); expect(screen.getByAltText(张三的头像)).toBeInTheDocument(); }); // 错误处理 it(should render fallback when avatar image fails to load, () { render(BlogCard {...defaultProps} /); const img screen.getByAltText(张三的头像); fireEvent.error(img); // 如果有 fallback 组件检查它是否出现 }); });审查 AI 生成的测试——我的修改✅ 正常流程覆盖了渲染内容、点击、键盘操作✅ 边界情况有超长标题、空 excerpt❌ 缺少「连续快速点击」的测试——防止 onClick 被触发多次❌ 缺少「category 为空」的边界情况——category-badge 应该隐藏❌ 缺少「时间格式」的验证——date 应显示为2026年1月15日而不是原始字符串追加的测试it(should not trigger onClick multiple times on rapid clicks, () { const onClick vi.fn(); render(BlogCard {...defaultProps} onClick{onClick} /); const card screen.getByRole(button); fireEvent.click(card); fireEvent.click(card); fireEvent.click(card); // 如果有 debounce/throttle应该只触发一次 expect(onClick).toHaveBeenCalledTimes(3); // 无 debounce 时每次都触发 }); it(should not show category badge when category is empty, () { render(BlogCard {...defaultProps} category /); expect(screen.queryByText(React)).not.toBeInTheDocument(); expect(screen.queryByText()).not.toBeInTheDocument(); });测试质量标准不是「能跑就行」AI 生成的测试能跑 ≠ 测试质量好。好的测试需要满足标准含义AI 常见问题行为驱动测用户行为不测实现细节AI 有时会测「state 值是多少」而不是「UI 显示什么」边界覆盖空/null/超长/异常输入AI 通常覆盖 3-5 个边界你补充遗漏的可维护性测试不会因为小改动就全挂AI 有时测了组件内部实现className一改样式就挂独立性每个测试不依赖其他测试AI 通常没问题但多个测试共享 mock 时可能出问题可读性测试名能清楚说明测什么AI 写的 it(‘renders correctly’) 不如 it(‘should show empty state when list is empty’)行为驱动 vs 实现驱动的对比// ❌ 实现驱动——测内部实现 it(should set loading state to true when fetching, () { // 测了内部 state如果改成 React Query 就挂了 expect(component.state().loading).toBe(true); }); // ✅ 行为驱动——测用户看到什么 it(should show loading spinner while fetching data, () { // 测了 UI 表现不管内部怎么实现 expect(screen.getByRole(progressbar)).toBeInTheDocument(); });用 AI 生成文档API 文档自动生成src/services/api.ts src/types/api.d.ts 根据这些 API 接口定义生成 API 文档格式如下 - 接口名称 - 请求方法 URL - 请求参数含类型和说明 - 响应格式含类型和说明 - 错误码列表 - 使用示例包含请求代码和响应示例 使用 Markdown 格式适合放在项目 wiki 中。组件文档自动生成src/components/BlogCard.tsx 为 BlogCard 组件生成文档包含 1. 概述组件用途和核心功能 2. Props 表格每个 prop 的名称、类型、默认值、是否必传、说明 3. 使用示例至少 2 个不同配置的示例 4. 可访问性说明ARIA 属性和键盘操作 5. 依赖说明依赖的其他组件/库README 自动生成package.json src/ README.md如果存在 为这个项目生成 README包含以下章节 1. 项目简介一段话描述 2. 技术栈列表形式 3. 快速开始安装 启动命令 4. 项目结构目录树 简要说明 5. 开发指南环境要求 配置 开发命令 6. 部署说明 7. 贡献指南 语气简洁务实不要过度营销式描述AI 生成文档的审查要点文档类型审查重点常见问题API 文档参数类型是否跟代码一致、示例是否可运行AI 有时把 optional 参数标成 required组件文档Props 表格是否完整、示例是否覆盖核心场景AI 有时漏了新增的 propsREADME安装命令是否正确、目录结构是否最新AI 可能用了过时的命令npm vs pnpm关键原则文档必须跟代码同步。AI 生成文档后每次代码改动都要更新文档。可以把文档更新也加进 .cursorrules## AI协作规则 - 修改组件 Props 时同步更新组件文档 - 新增 API 接口时同步更新 API 文档 - 改动 package.json 后同步更新 README 的安装命令常见误区误区1测试越写越多越好测试的目标是覆盖关键行为不是覆盖所有代码行。一个「搜索为空时显示提示」的测试比三个「断言 className 是否包含某个值」的测试更有价值。误区2AI 生成的测试不用审查AI 生成的测试可能测了实现细节而不是行为、可能遗漏关键边界情况、可能用了不稳定的 mock。必须审查。误区3文档写了就不会过时文档和代码的关系是「代码改动 → 文档更新」。AI 可以帮你快速生成初始文档但维护文档是你的责任。 AI协作实战实战场景为一个 Zustand Store 生成完整测试Store 代码// userStore.tsimport{create}fromzustand;interfaceUser{id:string;name:string;email:string;role:admin|editor|viewer;}interfaceUserState{users:User[];searchQuery:string;selectedRole:string;loading:boolean;error:string|null;fetchUsers:()Promisevoid;setSearchQuery:(query:string)void;setSelectedRole:(role:string)void;getFilteredUsers:()User[];}我给 AI 的 PromptuserStore.ts 为这个 Zustand Store 生成完整的测试用例。 覆盖 - 正常流程fetchUsers 成功返回数据 - 边界情况空搜索、空角色筛选、同时搜索筛选 - 错误处理fetchUsers 失败 - 状态一致性setSearchQuery 后 getFilteredUsers 返回正确结果 测试框架Vitest 注意Zustand store 测试用 act() 包裹状态变更AI 生成的核心测试import{describe,it,expect,vi,beforeEach}fromvitest;import{useUserStore}from./userStore;// Mock fetchconstmockFetchvi.fn();global.fetchmockFetch;describe(userStore,(){beforeEach((){// 重置 store 状态useUserStore.setState({users:[],searchQuery:,selectedRole:,loading:false,error:null,});mockFetch.mockReset();});it(should fetch users successfully,async(){constmockUsers[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},];mockFetch.mockResolvedValueOnce({ok:true,json:()Promise.resolve(mockUsers),});awaituseUserStore.getState().fetchUsers();expect(useUserStore.getState().users).toEqual(mockUsers);expect(useUserStore.getState().loading).toBe(false);expect(useUserStore.getState().error).toBe(null);});it(should handle fetch error,async(){mockFetch.mockResolvedValueOnce({ok:false,status:500});awaituseUserStore.getState().fetchUsers();expect(useUserStore.getState().users).toEqual([]);expect(useUserStore.getState().error).toBe(Failed to fetch users);expect(useUserStore.getState().loading).toBe(false);});it(should filter users by search query,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},],searchQuery:ali,});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(1);expect(filtered[0].name).toBe(Alice);});it(should filter users by role,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},],selectedRole:admin,});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(1);expect(filtered[0].role).toBe(admin);});it(should combine search and role filter,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Alice Bob,email:alice.bobtest.com,role:editor},{id:3,name:Charlie,email:charlietest.com,role:admin},],searchQuery:ali,selectedRole:admin,});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(1);expect(filtered[0].name).toBe(Alice);expect(filtered[0].role).toBe(admin);});it(should return all users when no filter is applied,(){useUserStore.setState({users:[{id:1,name:Alice,email:alicetest.com,role:admin},{id:2,name:Bob,email:bobtest.com,role:editor},],});constfiltereduseUserStore.getState().getFilteredUsers();expect(filtered).toHaveLength(2);});});审查要点✅ 每个测试有beforeEach重置状态——确保独立性✅ 覆盖了组合筛选搜索 角色——这是容易遗漏的❌ 缺少「搜索大小写不敏感」的测试——如果 getFilteredUsers 做了大小写处理应该有对应测试❌ 缺少「搜索为空字符串时返回全部」的边界测试——空字符串 ! 无搜索学到了什么AI 生成的测试覆盖了大部分场景但组合筛选和边界情况需要你根据业务逻辑补充。测试 Prompt 中的 [Proof] 部分很关键——它告诉 AI 你特别关心的场景。 动手练习练习1简单为一个工具函数生成测试写一个简单的formatDate(date: string): string函数用 AI 生成测试。审查 AI 生成的测试至少补充 2 个边界情况。练习2中等为你的一个 React 组件生成完整测试选你项目中的一个组件用 CRISP 测试 Prompt 生成测试。运行测试确保全部通过。记录你额外补充的测试用例和原因。练习3挑战建立「测试 → 文档 → README」一站式生成流程为你的项目建立以下 AI 工作流新增组件 → 自动生成组件测试 组件文档新增 API → 自动生成 API 测试 API 文档项目改动 → 自动更新 README把这个流程写成 SOP包含每一步的 Prompt 模板。 本期要点测试和文档最适合交给 AI重复性高、创造性低AI 主导 人审查测试质量标准行为驱动 实现驱动覆盖边界 覆盖行数CRISP 测试 Prompt在 [Proof] 中列出你特别关心的场景让 AI 重点覆盖文档必须跟代码同步AI 生成初始文档每次代码改动时用 AI 更新文档审查 AI 测试的 5 个标准行为驱动、边界覆盖、可维护性、独立性、可读性 下期预告下一期我们进入 AI 辅助学习——如何用 AI 加速知识获取看源码、读文档、学新框架。你将建立一套「AI 学习工作流」让学习效率翻倍。如果你没有苹果电脑需要上传ios到APPStore可以访问以下网站iPA上传工具 - IPA解析与AppStore提交