让 AI 写代码的正确姿势:1500 个测试用例背后的协作智慧 AI 工程实践 2025-10-30 0 浏览 0 点赞 长文 ## 引言:当 AI 遇到 1500 个测试用例 在 AI 代码生成工具遍地开花的今天,一个问题逐渐浮现:**为什么有些开发者能让 AI 成为高效助手,而另一些人却觉得 AI 写的代码"不靠谱"?** Simon Willison,Datasette 项目的创建者、Django 核心开发者,最近分享了他与 Claude Code 协作的实践经验。他的项目有一个令人印象深刻的数字:**1500 个自动化测试用例**。 这不是炫耀测试覆盖率,而是揭示了一个深刻的洞察:**让 AI 写代码的关键,不在于 AI 有多聪明,而在于你的代码库有多"可协作"**。 Simon 的五大实践经验,本质上都在回答同一个问题:**如何构建一个"人机友好"的代码库?** --- ## 一、自动化测试:AI 的"安全网"与"指南针" ### 1.1 为什么测试对 AI 如此重要? Simon 的第一条建议是:**优质的自动化测试至关重要**。他特别推荐 pytest,并分享了一个实际案例: **项目背景** - 1500 个测试用例 - 使用 pytest 框架 - Claude Code 作为开发助手 **AI 的智能行为** 1. **智能选择测试**:Claude Code 能识别当前代码变更,只运行相关的测试子集 2. **快速反馈**:几秒钟内得到测试结果,而不是等待完整测试套件(可能需要几分钟) 3. **最终验证**:在确认修改正确后,再运行完整测试套件 这种工作流程类似于人类开发者的"TDD(测试驱动开发)",但 AI 执行得更快、更一致。 ### 1.2 测试是 AI 的"安全网" 对于人类开发者,测试的价值是"防止回归"——确保新代码不会破坏旧功能。 对于 AI,测试的价值更加关键:**它是 AI 唯一可靠的"真相来源"**。 **为什么 AI 需要测试?** **问题 1:AI 不理解"业务逻辑"** AI 可以读懂代码语法,但不一定理解"为什么这样写"。测试用例通过"输入 → 输出"的方式,明确定义了预期行为。 示例: ```python def test_user_discount(): # VIP 用户享受 20% 折扣 user = User(type="VIP") price = calculate_price(user, 100) assert price == 80 ``` 这个测试告诉 AI:"VIP 用户的价格计算逻辑是 80% 的原价",比任何文档都清晰。 **问题 2:AI 容易"过度优化"** AI 可能会为了"代码简洁"而删除看似冗余的逻辑,但这些逻辑可能处理边缘情况。测试用例能捕获这些边缘情况。 示例: ```python def test_empty_cart(): # 空购物车应该返回 0,而不是报错 cart = Cart([]) assert cart.total() == 0 ``` 如果没有这个测试,AI 可能会写出在空购物车时崩溃的代码。 **问题 3:AI 需要"即时反馈"** 人类开发者可以"感觉"代码是否正确,但 AI 需要明确的反馈。测试失败 = 代码错误,测试通过 = 代码正确。 ### 1.3 测试是 AI 的"指南针" 除了"安全网",测试还是 AI 的"指南针"——指引 AI 朝正确方向前进。 **场景:重构代码** 假设你要求 AI:"重构 `calculate_price` 函数,提高可读性"。 - **没有测试**:AI 可能会改变函数逻辑,导致计算错误 - **有测试**:AI 会确保重构后所有测试仍然通过,保证行为不变 **场景:添加新功能** 假设你要求 AI:"添加学生折扣功能"。 - **没有测试**:AI 可能会破坏现有的 VIP 折扣逻辑 - **有测试**:AI 会先运行现有测试,确保新功能不影响旧功能 ### 1.4 pytest 为什么特别适合 AI? Simon 特别推荐 pytest,原因在于它的几个特性: **特性 1:简洁的语法** ```python def test_addition(): assert 1 + 1 == 2 ``` 没有复杂的类继承、没有冗长的断言方法,AI 可以轻松理解和生成。 **特性 2:强大的 fixture 系统** ```python @pytest.fixture def database(): db = create_test_database() yield db db.cleanup() def test_user_creation(database): user = database.create_user("Alice") assert user.name == "Alice" ``` Fixture 让测试的"准备工作"可复用,AI 可以学习这些模式。 **特性 3:参数化测试** ```python @pytest.mark.parametrize("input,expected", [ (1, 2), (2, 4), (3, 6), ]) def test_double(input, expected): assert double(input) == expected ``` 一个测试函数覆盖多个场景,AI 可以快速验证多种情况。 **特性 4:清晰的失败信息** ``` FAILED test_user.py::test_discount - AssertionError: assert 90 == 80 ``` AI 可以从失败信息中精确定位问题。 ### 1.5 实践建议:为 AI 优化测试 **建议 1:测试应该"自解释"** ```python # ❌ 不好:需要查看代码才能理解 def test_calc(): assert calc(10, 5) == 15 # ✅ 好:测试名称和注释说明意图 def test_calculate_price_with_vip_discount(): """VIP 用户购买 100 元商品,应享受 20% 折扣,支付 80 元""" user = User(type="VIP") price = calculate_price(user, 100) assert price == 80 ``` **建议 2:覆盖边缘情况** ```python def test_edge_cases(): assert calculate_price(None, 100) == 100 # 无用户 assert calculate_price(user, 0) == 0 # 零价格 assert calculate_price(user, -10) raises ValueError # 负价格 ``` **建议 3:保持测试独立** 每个测试应该能独立运行,不依赖其他测试的执行顺序。这让 AI 可以只运行相关测试。 **建议 4:快速执行** 避免测试中的网络请求、数据库操作(使用 mock 或 fixture),让 AI 能快速迭代。 --- ## 二、交互式测试:让 AI "看到"代码效果 ### 2.1 为什么 AI 需要"看到"效果? Simon 的第二条建议是:**赋予智能代理交互式测试代码的能力**。 对于 Web 项目,这意味着: - 给出启动开发服务器的说明 - 让 AI 能用 Playwright 或 curl 验证代码效果 **为什么这很重要?** 单元测试验证"函数逻辑",但无法验证"用户体验": - 按钮是否可点击? - 页面是否正确渲染? - API 是否返回正确的 HTTP 状态码? 交互式测试让 AI 能像人类一样"使用"应用,而不只是"阅读"代码。 ### 2.2 实践案例:Web 应用的交互式测试 **场景:修复登录页面的 bug** **传统方式** 1. AI 修改代码 2. 你手动启动服务器 3. 你手动打开浏览器测试 4. 你告诉 AI 是否修复成功 **交互式测试方式** 1. AI 修改代码 2. AI 自动启动开发服务器 3. AI 用 Playwright 自动测试登录流程 4. AI 根据测试结果决定是否需要进一步修改 **Playwright 测试示例** ```python async def test_login_flow(): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() # 访问登录页面 await page.goto("http://localhost:3000/login") # 填写表单 await page.fill("#username", "testuser") await page.fill("#password", "testpass") await page.click("#login-button") # 验证跳转到首页 await page.wait_for_url("http://localhost:3000/dashboard") assert "Welcome" in await page.content() ``` AI 可以运行这个测试,看到"测试通过"或"测试失败",并据此调整代码。 ### 2.3 curl 测试:API 的快速验证 对于 API 项目,curl 是更轻量的选择: **测试 API 端点** ```bash # 测试用户注册 curl -X POST http://localhost:8000/api/register \ -H "Content-Type: application/json" \ -d '{"username": "alice", "password": "secret"}' # 预期响应:{"status": "success", "user_id": 123} ``` AI 可以执行 curl 命令,解析响应,判断 API 是否正常工作。 ### 2.4 实践建议:构建"可观测"的开发环境 **建议 1:提供清晰的启动说明** 在项目根目录创建 `DEVELOPMENT.md`: ```markdown ## 启动开发服务器 ```bash npm run dev # 服务器将在 http://localhost:3000 启动 ``` ## 运行交互式测试 ```bash pytest tests/e2e/ ``` ``` **建议 2:使用健康检查端点** ```python @app.get("/health") def health_check(): return {"status": "ok", "version": "1.0.0"} ``` AI 可以先访问 `/health`,确认服务器已启动。 **建议 3:提供测试数据** ```python # tests/fixtures/test_data.py TEST_USER = { "username": "testuser", "password": "testpass" } ``` AI 可以使用这些测试数据,而不是生成随机数据(可能导致不一致)。 **建议 4:记录预期行为** ```python def test_api_response_format(): """ API 应返回以下格式: { "status": "success" | "error", "data": {...}, "message": "..." } """ response = client.get("/api/users") assert "status" in response.json() assert "data" in response.json() ``` --- ## 三、GitHub Issue 集成:AI 的"任务清单" ### 3.1 为什么 Issue 是理想的任务描述? Simon 的第三条建议是:**通过维护 GitHub issue 集合,并将问题 URL 直接粘贴到 Claude Code,实现项目管理和问题追踪**。 **Issue 的优势** **优势 1:结构化信息** - 标题:简洁的问题描述 - 正文:详细的上下文、复现步骤、预期行为 - 标签:bug、feature、priority 等分类 - 评论:讨论历史、解决方案建议 **优势 2:可追溯性** 每个 Issue 有唯一 URL,AI 可以引用:"我正在修复 #123"。 **优势 3:协作友好** 团队成员可以在 Issue 中讨论,AI 可以读取这些讨论,理解问题的完整背景。 ### 3.2 实践案例:从 Issue 到代码 **Issue #456:添加用户导出功能** ```markdown ## 描述 用户希望能导出自己的数据为 CSV 文件 ## 需求 - 添加 /api/export 端点 - 支持导出用户信息、订单历史 - 文件名格式:user_{user_id}_{timestamp}.csv ## 验收标准 - [ ] API 返回正确的 CSV 格式 - [ ] 包含所有必要字段 - [ ] 添加单元测试 ``` **与 AI 的对话** ``` 你:请实现 https://github.com/myproject/issues/456 AI:我已阅读 Issue #456,将添加用户导出功能。 我会: 1. 创建 /api/export 端点 2. 实现 CSV 生成逻辑 3. 添加测试用例 开始实现... ``` AI 可以从 Issue 中提取: - 功能需求(导出为 CSV) - 技术细节(端点路径、文件名格式) - 验收标准(测试要求) ### 3.3 实践建议:编写"AI 友好"的 Issue **建议 1:使用模板** ```markdown ## 问题描述 [简洁描述问题] ## 复现步骤 1. ... 2. ... ## 预期行为 [应该发生什么] ## 实际行为 [实际发生了什么] ## 相关代码 [文件路径或代码片段] ## 建议方案 [可选:你的解决思路] ``` **建议 2:附加上下文** - 截图或录屏(对于 UI 问题) - 错误日志(对于 bug) - 相关 Issue 链接(对于关联问题) **建议 3:明确优先级** 使用标签:`priority:high`、`priority:low`,让 AI 知道哪些问题更重要。 **建议 4:更新 Issue 状态** 当 AI 完成修复后,在 Issue 中评论:"已由 Claude Code 修复,commit: abc123"。 --- ## 四、文档的"反直觉"价值 ### 4.1 文档对 AI 的帮助没想象中大 Simon 的第四条建议是最反直觉的:**文档对智能代理的帮助没想象中大**。 他的项目文档详尽,但发现:**LLM 读代码比人更快,文档主要帮我发现需要更新的地方**。 **为什么会这样?** **原因 1:AI 读代码的速度** - 人类:阅读 1000 行代码可能需要 1 小时 - AI:几秒钟内理解整个代码库的结构 **原因 2:代码是"真相"** - 文档可能过时("我们使用 MySQL",但代码已切换到 PostgreSQL) - 代码永远是最新的(它就是正在运行的系统) **原因 3:AI 擅长"逆向工程"** AI 可以从代码推断出: - 这个函数做什么 - 这个类的职责是什么 - 这个模块如何与其他模块交互 ### 4.2 文档的真正价值:发现"不一致" Simon 发现文档的主要价值是:**当 AI 指出"文档与代码不一致"时,提醒你更新文档**。 **案例** ``` AI:我注意到 README 中说"使用 Redis 缓存", 但代码中使用的是 Memcached。 需要更新文档吗? ``` 这种"不一致检测"对人类很有价值——它帮助保持文档的准确性。 ### 4.3 什么样的文档对 AI 有用? 虽然详细的技术文档价值有限,但某些类型的文档仍然重要: **有用的文档 1:架构决策记录(ADR)** ```markdown ## ADR-001:选择 PostgreSQL 而非 MySQL ### 背景 我们需要选择关系型数据库 ### 决策 使用 PostgreSQL ### 理由 - 更好的 JSON 支持 - 更强大的全文搜索 - 团队更熟悉 ### 后果 - 需要学习 PostgreSQL 特有功能 - 部署复杂度略增 ``` 这种文档解释"为什么",而代码只展示"是什么"。 **有用的文档 2:业务规则** ```markdown ## 折扣规则 1. VIP 用户享受 20% 折扣 2. 学生用户享受 10% 折扣 3. 折扣不可叠加 4. 特价商品不参与折扣 ``` 这些规则可能分散在多个函数中,文档提供了"全局视图"。 **有用的文档 3:开发指南** ```markdown ## 添加新的 API 端点 1. 在 `routes/` 中创建路由 2. 在 `controllers/` 中实现逻辑 3. 在 `tests/` 中添加测试 4. 更新 `openapi.yaml` ``` 这是"工作流程",帮助 AI 遵循团队规范。 ### 4.4 实践建议:为 AI 优化文档 **建议 1:保持文档简洁** 不要写"代码的翻译"(AI 可以自己读代码),而是写"代码背后的意图"。 **建议 2:使用"可执行文档"** ```markdown ## 启动项目 ```bash docker-compose up ``` ## 运行测试 ```bash pytest ``` ``` AI 可以直接执行这些命令。 **建议 3:文档即测试** 使用 doctest(Python)或类似工具: ```python def calculate_discount(price, user_type): """ 计算折扣后价格 >>> calculate_discount(100, "VIP") 80.0 >>> calculate_discount(100, "student") 90.0 """ ... ``` 文档中的示例可以作为测试运行。 --- ## 五、代码质量工具:AI 的"行为规范" ### 5.1 为什么质量工具对 AI 重要? Simon 的第五条建议是:**代码质量工具不能少——linters、类型检查器、自动格式化工具等,让智能代理有据可依**。 **质量工具的作用** **作用 1:统一代码风格** - Prettier(JavaScript)、Black(Python):自动格式化 - AI 生成的代码自动符合团队规范 **作用 2:捕获常见错误** - ESLint、Pylint:检测潜在 bug - AI 可以在提交前自动修复这些问题 **作用 3:强制类型安全** - TypeScript、mypy:类型检查 - AI 生成的代码必须通过类型检查 ### 5.2 实践案例:类型检查的威力 **没有类型检查** ```python def calculate_price(user, amount): if user.type == "VIP": return amount * 0.8 return amount ``` AI 可能会传递错误的参数类型: ```python calculate_price("VIP", "100") # 字符串而非数字 ``` **有类型检查** ```python def calculate_price(user: User, amount: float) -> float: if user.type == "VIP": return amount * 0.8 return amount ``` mypy 会报错: ``` error: Argument 1 has incompatible type "str"; expected "User" ``` AI 看到错误后,会自动修正。 ### 5.3 推荐的质量工具组合 **Python 项目** - **Black**:代码格式化 - **isort**:import 排序 - **mypy**:类型检查 - **pylint**:代码质量检查 - **pytest**:测试框架 **JavaScript/TypeScript 项目** - **Prettier**:代码格式化 - **ESLint**:代码质量检查 - **TypeScript**:类型系统 - **Jest**:测试框架 **配置示例(Python)** ```toml # pyproject.toml [tool.black] line-length = 88 [tool.mypy] strict = true [tool.pylint] max-line-length = 88 ``` ### 5.4 实践建议:自动化质量检查 **建议 1:Pre-commit hooks** ```yaml # .pre-commit-config.yaml repos: - repo: https://github.com/psf/black rev: 23.0.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.0.0 hooks: - id: mypy ``` AI 提交代码前,自动运行格式化和检查。 **建议 2:CI/CD 集成** ```yaml # .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run linters run: | black --check . mypy . - name: Run tests run: pytest ``` **建议 3:实时反馈** 在 IDE 中配置实时 linting,AI 可以立即看到错误。 --- ## 六、核心洞察:人机协作的"对称性" ### 6.1 Simon 的核心观点 Simon 的总结是整篇文章的精华: **"凡是让人类更好维护代码库的手段,基本上也同样能提升智能代理的工作效率。"** 这揭示了人机协作的本质:**不是"AI 替代人",而是"好的工程实践同时服务于人和 AI"**。 ### 6.2 为什么会有这种"对称性"? **原因 1:共同的目标** - 人类需要:可读、可维护、可测试的代码 - AI 需要:可理解、可验证、可迭代的代码 **原因 2:共同的挑战** - 人类面临:复杂度、不确定性、边缘情况 - AI 面临:同样的复杂度、不确定性、边缘情况 **原因 3:共同的工具** - 测试、文档、质量工具——这些都是"外部化的知识" - 人类通过它们理解代码,AI 也通过它们理解代码 ### 6.3 实践启示:不要为 AI "特殊对待" **错误思路** "我需要为 AI 写特殊的文档/测试/工具" **正确思路** "我需要提升代码库的整体质量,这会同时帮助人和 AI" **具体行动** - ✅ 写更好的测试(人和 AI 都受益) - ✅ 使用类型系统(人和 AI 都受益) - ✅ 保持代码简洁(人和 AI 都受益) - ❌ 为 AI 写"特殊注释"(只有 AI 受益,增加维护负担) --- ## 结语:未来的开发是"人机共生" Simon 的实践经验揭示了一个重要趋势:**未来的软件开发不是"人写代码"或"AI 写代码",而是"人机协作写代码"**。 **这种协作的基础是**: 1. **优质的测试**:让 AI 有"安全网"和"指南针" 2. **交互式验证**:让 AI "看到"代码效果 3. **结构化任务**:让 AI 理解"要做什么" 4. **简洁的文档**:让 AI 理解"为什么这样做" 5. **质量工具**:让 AI 遵循"团队规范" **关键洞察**: - 不要把 AI 当作"魔法工具",而要把它当作"新团队成员" - 不要为 AI "降低标准",而要为 AI "提升基础设施" - 不要期待 AI "理解一切",而要给 AI "明确的反馈" **当你的代码库对人类友好时,它也会对 AI 友好。** **当你的工程实践对人类有效时,它也会对 AI 有效。** 这不是巧合,而是软件工程的本质:**好的代码是可协作的代码,无论协作者是人还是 AI**。 --- **核心要点总结** | 实践 | 对人类的价值 | 对 AI 的价值 | |------|-------------|-------------| | **自动化测试** | 防止回归、快速反馈 | 安全网、指南针、真相来源 | | **交互式测试** | 验证用户体验 | "看到"代码效果、端到端验证 | | **GitHub Issue** | 任务管理、协作讨论 | 结构化任务描述、上下文理解 | | **简洁文档** | 理解架构决策 | 理解"为什么"、发现不一致 | | **质量工具** | 统一风格、捕获错误 | 行为规范、自动修复 | **实践建议** 1. **先投资测试**:1500 个测试用例不是负担,而是资产 2. **让 AI 能"运行"代码**:提供启动说明、健康检查 3. **用 Issue 管理任务**:结构化描述 > 自然语言对话 4. **文档求精不求多**:写"为什么",不写"是什么" 5. **质量工具全覆盖**:格式化、linting、类型检查、测试 **关键洞察** - 人机协作的"对称性":好的工程实践同时服务于人和 AI - 不要"特殊对待" AI:提升整体质量,而非为 AI 定制 - 未来是"人机共生":AI 是新团队成员,不是魔法工具 原文链接 Simon Willison 的完整博客文章 pytest 文档 Python 测试框架官方文档 Playwright 端到端测试框架官方网站 Datasette Simon Willison 的开源项目 - GitHub Claude Code Anthropic Claude AI 产品页面 #AI 编程 #Claude #pytest #人机协作 #代码质量 #开发实践 #自动化测试