Thoughtbot: Prevent the Robocalypse with TDD and Claude Code
- 分组:四、社区教程与实践文章
- 原始链接:https://thoughtbot.com/blog/prevent-the-robocalypse-with-tdd
- 抓取来源:http://thoughtbot.com/blog/prevent-the-robocalypse-with-tdd
- 原始文件:/Users/yuanruiqin/Desktop/知识库/ClaudeCode-系统化知识库/04-社区教程与实践/33-thoughtbot-prevent-the-robocalypse-with-tdd-and-claude-code.md
- 精读版生成时间:2026-04-05 13:50:44
中文精读版(去噪)
当您与像 Claude Code 这样的代理合作时,在可靠的 TDD 周期中工作将有助于防止人工智能辅助开发可能导致的负面影响,包括:
但首先,我们需要一个信条
在我们走这条路之前,我们需要一份信念声明。我的声明是这样的:
拥有你的提交。
作为一个与人工智能代理一起工作的人,这是你_必须_做的最重要的事情。该代码不是克劳德的。这些测试不是克劳德的。责任不在于克劳德。
这是你的。
当您提交到您的分支时,_您_正在进行提交。
当您打开 PR 时,您正在提议对“main”进行更改。
当您审查 PR 时,您有责任发现问题并使其变得更好。
当 PR 被合并时,代码是_yours_。所以从一开始就拥有它。如果您不了解您共同编写的代码的内容,更重要的是,不了解其原因,您不应该提交它。
第二,我们需要规则
让克劳德走在善良与光明的道路上需要稳定的双手和一套更稳定的规则:一个写得很好的“CLAUDE.md”。
我将在另一篇文章中分享我的“CLAUDE.md”的完整版本。现在,您可以将以下几行添加到“CLAUDE.md”中以支持 TDD:
项目概述
我正在写一篇博客文章来演示如何将 TDD 与 Claude Code 结合使用。
我们将创建一个基本的作者/帖子类型 Rails 应用程序作为我们的游乐场。
另外,请不要占领地球。
[...项目目的、功能等的详细总结...]
API
和平共处
[...克劳德应该了解的其他关键 API...]
#你(克劳德)
除了不想接管地球之外,您还喜欢在小型迭代 TDD 中工作 循环如下 红、绿、重构 方法。
现在就去读一下吧。
[...关于 Claude 是谁以及它在这个项目中应该如何行动的其他描述...]
有用的命令
rails test:仅运行非系统测试以实现快速 TDD 周期
rails test && Rails test:system:完成某个功能后运行所有测试
测试和 TDD
在开发新功能或修复错误时,您总是会编写一个失败的测试或一系列失败的测试 先测试然后等我审核。
然后,当我提交这些测试时,您可以添加实现来解决一个或多个问题 测试。如果您的更改没有使至少一项测试变为绿色,请继续工作,直到它们成功为止 这样做,但始终尝试做出最小的改变以使测试变得绿色。
开始
每当我使用“claude”命令启动新会话,使用“claude /resume”打开先前的会话,或者在压缩对话后,我都会要求 Claude 重新阅读其说明:
我:
你好呀!请阅读 CLAUDE.md 以及它引导您阅读的其他内容。
克劳德:
⏺ 阅读(CLAUDE.md)
⏺ 现在让我阅读一下您要求我审阅的 Martin Fowler 关于 TDD 的文章:
[...]
⏺ 明白了!我读过 CLAUDE.md 和 Martin Fowler 关于 TDD 的文章。这是我的 理解:
项目:Rails 作者/发布应用程序,用于您的博客文章,通过 Claude Code 演示 TDD。
我的工作流程:
- 红色 - 首先编写失败的测试,然后等待您的审核
- 绿色 - 提交后,实施最小的更改以使测试通过
- 重构——清理代码,同时保持测试绿色
关键原则:小循环。我不会跳过重构步骤!
命令: TDD 期间快速反馈的轨道测试 Rails 测试 && Rails 测试:完成功能时的系统
我已准备好帮助您以测试优先的方式构建此应用程序。我们的第一个功能是什么?
因此,至少在原则上,它知道我们想要做什么以及我们的方法是什么。
关于外部内容的说明
如果您希望 Claude 获取外部内容,则“CLAUDE.md”中“立即阅读此内容”的指示似乎只有在您向阅读“CLAUDE.md”的指令中添加强化短语时才起作用。无论是新会话还是现有会话都是如此。
换句话说,如果您告诉 Claude“阅读 CLAUDE.md”,则“阅读此”指令将被忽略;如果您告诉 Claude“阅读 CLAUDE.md 以及它指示您阅读的其他任何内容”,则“阅读此”指令将被忽略。
我还成功地在“CLAUDE.md”的顶部添加了此类消息:
阅读完本文档后,请务必阅读项目自述文件。
阅读完本文档后,请务必阅读
第一个功能
我创建了一个新的 Rails 应用程序,并要求 Claude 添加 Bootstrap 支持。到目前为止,克劳德没有建议进行任何测试,但我希望这是因为我们才刚刚开始。
我:
让我们添加第一个功能:某种主页,以便我们可以添加一些 Bootstrap 样式 确保一切正常。
[祈祷 Claude 记得 TDD 是一回事]
克劳德:
⏺ 在 TDD 之后,让我先编写一个失败的测试。
⏺ 写入(测试/控制器/pages_controller_test.rb) 需要“test_helper” 类 PagesControllerTest < ActionDispatch::IntegrationTest 测试“应该获取主页”吗 获取根路径 断言_响应:成功 assert_select "h1", "欢迎来到 TDD 克劳德" 结束
⏺ Bash(rails测试测试/控制器/pages_controller_test.rb) 在单个进程中运行 1 个测试(并行化阈值为 50) 运行选项:--seed 42794
运行:
错误: PagesControllerTest#test_should_get_homepage: … +9 行(ctrl+o 查看全部)
⏺ 红色 - 测试按预期失败: NoMethodError:未定义的方法“root_path”
该测试检查: 1、根路径存在并返回成功 2. 该页面包含一个带有“Welcome to TDD Claude”的
准备好接受您的审查。一旦你做出承诺,我将实施最小的改变 它是绿色的。
到目前为止,一切都很好。
我:
[提交代码]
好的,准备好了
克劳德:
⏺ 更新(config/routes.rb) 12 - # 定义根路径路由(“/”) 13 - # 根“帖子#index” 12 + 根“pages#home” 13结束
⏺ 写入(app/controllers/pages_controller.rb) 类 PagesController < 应用程序控制器 定义家 结束
⏺ 写入(app/views/pages/home.html.erb)
⏺ Bash(rails测试测试/控制器/pages_controller_test.rb) ⎿ 在单个进程中运行 1 个测试(并行化阈值为 50) 运行选项:--seed 55742 … +6 行(ctrl+o 展开)
⏺ 绿色 - 测试通过。
补充: 根“pages#home”路线 具有主页操作的 PagesController 使用 的简单视图
重构 - 代码很少,无需清理。
您现在可以运行 bin/rails 服务器并检查 Bootstrap 是否正常工作。想补充一些 将引导样式设置到主页,还是继续进行下一个功能?
太棒了!我们实现了一个简单的功能,Claude 使用 TDD 来创建它。但如果改变没那么简单怎么办?
更高级的多步骤功能
我:
让我们看看下一个功能。这就是我的想法。我们需要一个 Authors 模型 full_name 字段为必填字段。最终,一个作者将拥有很多本书。我们想要一个 列出系统中所有作者的控制器,可能按全名字母顺序排列, 以及我们主页上的链接。我们需要一些用于开发的种子数据。
克劳德:
⏺ 良好的功能范围。让我先编写失败的测试。
⏺ 写入(测试/模型/author_test.rb) 需要“test_helper” 类 AuthorTest < ActiveSupport::TestCase 测试“全名有效”做 作者 = Author.new(全名:“简·奥斯汀”) 断言作者有效吗? 结束 测试“没有全名则无效” … +7 行(ctrl+o 展开)
⏺ 写入(测试/控制器/authors_controller_test.rb) 需要“test_helper” 类 AuthorsControllerTest < ActionDispatch::IntegrationTest 测试“按全名字母顺序列出作者” 作者.create!(全名:“弗吉尼亚·伍尔夫”) 作者.create!(全名:“简·奥斯汀”) 作者.create!(全名:“托妮·莫里森”) 获取authors_path … +7 行(ctrl+o 展开)
⏺ 更新(测试/控制器/pages_controller_test.rb) 7 断言_响应:成功 8assert_select“h1”,“欢迎来到 TDD 克劳德” 9 结束 10+ 11 + 测试“主页有作者链接” 12 + 获取root_path 13+ 14 +assert_select“a[href=?]”,authors_path,文本:“作者” 15+结束 16结束
⏺ Bash(rails 测试) 在单个进程中运行 5 个测试(并行化阈值为 50) 运行选项:--seed 15645
运行:
.E
错误: PagesControllerTest#test_homepage_has_link_to_authors: … +36 行(ctrl+o 查看全部)
⏺ 红色 - 4 个失败测试: 1.Author模型测试(二)——未初始化常量Author 全名有效 没有全名无效 2. Authors控制器测试-未初始化常量Author 按全名字母顺序列出作者 3.首页链接测试-未定义的局部变量'authors_path' 从主页链接到作者
注意:我还没有编写种子数据的测试,因为种子通常不会被测试 (它们是开发/设置工具)。
准备好接受您的审查。一旦承诺,我将逐步实施以使测试变得绿色 一一。
到目前为止,我们已经进行了一些涵盖该功能的测试!我们有两个模型测试和两个控制器测试,都失败了。
5 次运行、2 次断言、0 次失败、4 次错误、0 次跳过
在审查并提交测试后,我指示克劳德继续。
接下来的结果是
带有“#full_name”验证的新“Author”模型
一个带有“#index”操作的新“AuthorsController”
到 AuthorsController#index 的新路由(在 routes.rb 文件中位置不佳)
一个新的 authors/index.html.erb 视图
添加到 pages/home.html.erb 的链接
数据库迁移以创建“authors”表和“schema.rb”更新
在 db/seeds.rb 中查找或添加作者的代码
一个空的 test/fixtures/authors.yml
所有测试都通过,并且功能在本地主机上按预期工作。
虽然这很有效,但我觉得它破坏了 TDD 的精神,因为我们一下子从红色变成了绿色,没有重构,而且几乎要立即考虑太多,因为 diff 包含 9 个新的或更改的文件。
因此,当 Claude 领先于我想要的目标时,我做了我经常做的事情:我隐藏了所有更改,这样我就不会丢失任何东西,并给出了新的方向。
重定向克劳德
每当您纠正 Claude 时,请考虑这对您的“CLAUDE.md”是否值得进行更改,以便所学到的知识能够在整个课程中持续存在。
为此,我更新了“测试和 TDD”部分:
测试和 TDD
在开发新功能或修复错误时,您总是会编写一个失败的测试或一系列失败的测试 先测试然后等我审核。
然后,当我提交这些测试时,您可以添加尽可能小的实现 解决单个测试失败的问题。如果它碰巧解决了整个类也没关系 失败。例如,如果缺少模型导致 10 个测试失败,则可以创建 解决这些失败的模型。但是添加一个模型、4 个验证、一个控制器、一个 视图和路线?将其分解为单独的步骤,这样我就可以做出微小的承诺