news 2026/10/20 15:14:20

第26期 | 用AI写测试与文档

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第26期 | 用AI写测试与文档

第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} className="blog-card" role="button" tabIndex={0}> <span className="category-badge">{category}</span> <h3>{title}</h3> <p className="line-clamp-2">{excerpt}</p> <div className="author-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 生成的测试——我的修改:

  1. ✅ 正常流程覆盖了:渲染内容、点击、键盘操作
  2. ✅ 边界情况有:超长标题、空 excerpt
  3. ❌ 缺少「连续快速点击」的测试——防止 onClick 被触发多次
  4. ❌ 缺少「category 为空」的边界情况——category-badge 应该隐藏
  5. ❌ 缺少「时间格式」的验证——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 有时漏了新增的 props
README安装命令是否正确、目录结构是否最新AI 可能用了过时的命令(npm vs pnpm)

关键原则:文档必须跟代码同步。AI 生成文档后,每次代码改动都要更新文档。可以把文档更新也加进 .cursorrules:

## AI协作规则 - 修改组件 Props 时,同步更新组件文档 - 新增 API 接口时,同步更新 API 文档 - 改动 package.json 后,同步更新 README 的安装命令

常见误区

误区1:测试越写越多越好
测试的目标是覆盖关键行为,不是覆盖所有代码行。一个「搜索为空时显示提示」的测试比三个「断言 className 是否包含某个值」的测试更有价值。

误区2:AI 生成的测试不用审查
AI 生成的测试可能测了实现细节而不是行为、可能遗漏关键边界情况、可能用了不稳定的 mock。必须审查。

误区3:文档写了就不会过时
文档和代码的关系是「代码改动 → 文档更新」。AI 可以帮你快速生成初始文档,但维护文档是你的责任。

🤖 AI协作实战

实战场景:为一个 Zustand Store 生成完整测试

Store 代码:

// userStore.tsimport{create}from'zustand';interfaceUser{id:string;name:string;email:string;role:'admin'|'editor'|'viewer';}interfaceUserState{users:User[];searchQuery:string;selectedRole:string;loading:boolean;error:string|null;fetchUsers:()=>Promise<void>;setSearchQuery:(query:string)=>void;setSelectedRole:(role:string)=>void;getFilteredUsers:()=>User[];}

我给 AI 的 Prompt:

@userStore.ts 为这个 Zustand Store 生成完整的测试用例。 覆盖: - 正常流程:fetchUsers 成功返回数据 - 边界情况:空搜索、空角色筛选、同时搜索+筛选 - 错误处理:fetchUsers 失败 - 状态一致性:setSearchQuery 后 getFilteredUsers 返回正确结果 测试框架:Vitest 注意:Zustand store 测试用 act() 包裹状态变更

AI 生成的核心测试:

import{describe,it,expect,vi,beforeEach}from'vitest';import{useUserStore}from'./userStore';// Mock fetchconstmockFetch=vi.fn();global.fetch=mockFetch;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:'alice@test.com',role:'admin'},{id:'2',name:'Bob',email:'bob@test.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:'alice@test.com',role:'admin'},{id:'2',name:'Bob',email:'bob@test.com',role:'editor'},],searchQuery:'ali',});constfiltered=useUserStore.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:'alice@test.com',role:'admin'},{id:'2',name:'Bob',email:'bob@test.com',role:'editor'},],selectedRole:'admin',});constfiltered=useUserStore.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:'alice@test.com',role:'admin'},{id:'2',name:'Alice Bob',email:'alice.bob@test.com',role:'editor'},{id:'3',name:'Charlie',email:'charlie@test.com',role:'admin'},],searchQuery:'ali',selectedRole:'admin',});constfiltered=useUserStore.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:'alice@test.com',role:'admin'},{id:'2',name:'Bob',email:'bob@test.com',role:'editor'},],});constfiltered=useUserStore.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 工作流:

  1. 新增组件 → 自动生成组件测试 + 组件文档
  2. 新增 API → 自动生成 API 测试 + API 文档
  3. 项目改动 → 自动更新 README
    把这个流程写成 SOP,包含每一步的 Prompt 模板。

📌 本期要点

  1. 测试和文档最适合交给 AI:重复性高、创造性低,AI 主导 + 人审查
  2. 测试质量标准:行为驱动 > 实现驱动,覆盖边界 > 覆盖行数
  3. CRISP 测试 Prompt:在 [Proof] 中列出你特别关心的场景,让 AI 重点覆盖
  4. 文档必须跟代码同步:AI 生成初始文档,每次代码改动时用 AI 更新文档
  5. 审查 AI 测试的 5 个标准:行为驱动、边界覆盖、可维护性、独立性、可读性

🔗 下期预告

下一期我们进入 AI 辅助学习——如何用 AI 加速知识获取:看源码、读文档、学新框架。你将建立一套「AI 学习工作流」,让学习效率翻倍。
如果你没有苹果电脑,需要上传ios到APPStore可以访问以下网站
iPA上传工具 - IPA解析与AppStore提交

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 15:13:44

100 01黄大年茶思屋榜文第100期 第1题 AI集群中的任务调度和碎片卡整理技术

黄大年茶思屋榜文第100期 第1题 AI集群中的任务调度和碎片卡整理技术 摘要 针对大规模AI集群中GPU资源碎片化导致的利用率低下问题&#xff0c;本文提出一套基于“预占延迟重调度”的轻量化调度与碎片整理方案。该方案无需修改底层容器运行时或GPU驱动&#xff0c;仅通过调度器…

作者头像 李华
网站建设 2026/10/20 15:14:17

【BUAA-OO-2026】第四单元:UML建模语言

文章目录 前言正向建模与两阶类图本单元架构设计第十三次作业第十四次作业第十五次作业最终架构 代码设计与 UML 模型的追踪关系类图与代码的对应状态图与代码的对应顺序图与代码的对应追踪关系总结 大模型辅助正向建模的体验架构设计思维的演进第一单元&#xff1a;从表达式层…

作者头像 李华
网站建设 2026/10/20 15:14:17

pytorch15->vgg16的修改,保存,加载

import torch import torchvision from torch import nn# 1. 加载预训练模型 print("" * 50) print("1. 加载预训练 VGG16 模型") vgg16 torchvision.models.vgg16(pretrainedTrue) print(vgg16) # 会打印出完整的网络结构# 2. 修改模型 (适配 CIFAR-…

作者头像 李华
网站建设 2026/10/14 3:35:15

UEFI基础知识学习2

一、 UEFI Driver Binding 模型 1、简要描述 UEFI Driver Binding 是 UEFI Driver 管理设备的标准模型。 UEFI Driver 加载后&#xff0c;通过安装 EFI_DRIVER_BINDING_PROTOCOL到Driver Image Handle上&#xff0c;向系统声明自己可以识别、启动和停止某类设备Controller。 2、…

作者头像 李华
网站建设 2026/10/16 6:18:44

2026年猫山郎榴莲冰皮月饼测评:究竟值不值得买?

在消费升级的大背景下&#xff0c;月饼市场也迎来了新的变革。消费者不再满足于传统口味的月饼&#xff0c;对具有独特风味和高品质的月饼需求日益增长。榴莲冰皮月饼作为近年来的热门品类&#xff0c;受到了众多消费者的关注。然而&#xff0c;市场上榴莲冰皮月饼品牌众多&…

作者头像 李华