跟AI一起结对编程,测试驱动开发
乙醇 创建于 9 months 之前
最后更新: 9 months 之前
阅读数: 728
martin fowler的博客上新发了一篇关于AI和TDD的文章,先翻译一下,然后聊聊我的看法。
使用测试驱动开发(TDD)与 GitHub Copilot 编码助手
AI 编程助手如 GitHub Copilot 的出现意味着我们不再需要测试了吗?TDD 将过时吗?为了回答这个问题,我们来看一下 TDD 如何帮助软件开发的两点:提供良好的反馈,以及在解决问题时“分治法”。
TDD 提供良好的反馈 良好的反馈要快速和准确。在这两方面,没有什么能比一个编写良好的单元测试更好。不论是手动测试,文档,代码审查,甚至是生成式 AI,都不能取代。事实上,大型语言模型提供无关信息,甚至 hallucinate。当使用 AI 编程助手时,尤其需要 TDD。我们需要对自己编写的代码快速准确的反馈,也同样需要对 AI 编程助手编写的代码快速准确的反馈。
TDD 通过分治法解决问题 通过分治法解决问题意味着较小的问题可以比较大的问题更早解决。这实现了持续集成,基于主线的开发,最终实现持续交付。但是如果 AI 助手为我们编写了代码,我们是否真的还需要所有这些?
是的。大语言模型很少能在一次提示后就提供我们所需的确切功能。所以迭代开发还没有走到尽头。此外,大语言模型似乎在通过思路提示的增量方式解决问题时“激发了推理”(参见相关研究)。基于 LLM 的 AI 编程助手在分治法解决问题时表现最好,而 TDD 是我们在软件开发中所做的。
使用 GitHub Copilot 的 TDD 技巧
Thoughtworks 从年初开始就一直在使用带有 TDD 的 GitHub Copilot。我们的目标是实验、评估和发展一系列围绕使用该工具的有效实践。
0. 开始
从一个空白的测试文件开始并不意味着从一个空白的上下文开始。我们通常从一个带一些粗略笔记的用户故事开始。我们也会与配对伙伴讨论一个起点。
所有这些上下文都是 Copilot 在我们把它放入一个打开的文件之前(例如测试文件顶部)“看不到”的。Copilot 可以处理拼写错误、点式格式、糟糕的语法等等。但是它无法处理一个空白文件。
一些对我们有效的启动上下文示例:
- ASCII 艺术画布
- 验收标准
- 引导假设,例如:
- "不需要 GUI"
- "使用面向对象编程"(而不是函数式编程) Copilot 使用打开的文件作为上下文,所以同时保持测试文件和实现文件打开(例如并排)大大提高了 Copilot 的代码补全能力。
1. 红色
我们首先编写一个描述性的测试示例名称。名称越描述性越好,Copilot 代码补全的表现也越好。
我们发现给出Given-When-Then结构有三方面帮助。首先,它提醒我们提供业务背景。其次,它允许 Copilot 为测试示例提供丰富且富有表现力的命名建议。第三,它揭示了 Copilot 从文件顶部上下文(如前一节所述)对问题的“理解”。
例如,如果我们正在进行后端编码,Copilot 将我们的测试示例名称补全为“给定用户......点击购买按钮”,这告诉我们应该更新文件顶部的上下文以指定“假设没有 GUI”或“此测试套件与 Python Flask 应用程序的 API 端点接口”。
更多要注意的“坑”:
- Copilot 可能会一次性补全多个测试。这些测试往往毫无用处(我们删除它们)。
- 随着我们添加更多测试,Copilot 将一次补全多行而不是一次补全一行。它通常会从测试名称中推断出正确的“arrange”和“act”步骤。
- 这里的坑:它不太常推断出正确的“assert”步骤,所以在进入“绿色”步骤之前,我们特别注意确保新的测试失败是正确的。
2. 绿色
TDD 表示为一个三部分轮子,右上方一 third 突出显示为“绿色”
现在我们准备好让 Copilot 在实现中提供帮助了。一个已经存在的,富有表现力和可读性的测试套件可以最大限度地发挥 Copilot 在此步骤的潜力。
也就是说,Copilot 通常无法采取“小步骤”。例如,在添加新方法时,“小步骤”意味着返回一个硬编码的值以通过测试。到目前为止,我们还没有能够说服 Copilot 采用这种方法。
回填测试 Copilot 并没有采取“小步骤”,而是跳跃前进,提供的功能通常相关,但还没有经过测试。作为变通方法,我们“回填”缺失的测试。虽然这与标准 TDD 流程不同,但我们还没有看到我们的变通方法存在任何严重问题。
删除并重新生成 对于需要更新的实现代码,让 Copilot 从头重新生成代码是让它参与的最有效方法。如果这失败了,删除方法内容并使用代码注释逐步描述方法可能有助于解决。如果这也不行,最好的前进方法可能是暂时关闭 Copilot 并手动编写解决方案。
3. 重构
TDD 中的重构意味着进行增量更改以提高代码库的可维护性和可扩展性,同时保留行为(和一个可工作的代码库)。
对此,我们发现 Copilot 的能力有限。考虑两种场景:
“我知道想尝试的重构举措”:IDE 重构快捷方式和多光标选择等功能可以更快地帮助我们实现目标,而不是 Copilot。
“我不知道采取哪种重构举措”:Copilot 代码补全无法引导我们完成重构。然而,Copilot 聊天可以在 IDE 中提出代码改进建议。我们已经开始探索该功能,并看到它在小范围内提出有用建议的前景。但我们在更大规模的重构建议(即超出单个方法/函数)方面还没有太多成功。
有时候我们知道重构的举措但不知道执行它所需的语法。例如,创建一个测试模拟以允许注入一个依赖项。对于这些情况,Copilot 可以通过代码注释中的提示提供内联答案。这省去了我们切换到文档或网络搜索的上下文。
总结
常说的“垃圾进,垃圾出”适用于数据工程和生成式 AI 以及大型语言模型。换句话说,更高质量的输入允许更好地利用大语言模型的功能。在我们的例子中,TDD 保持了高水平的代码质量。与其他情况相比,这高质量的输入导致了更好的 Copilot 性能。
因此,我们建议将 Copilot 与 TDD 一起使用,希望上述技巧能帮助您做到这一点。
我的想法
- 在给出了足够的上下文以及使用一些正确技巧的前提下,用copilot写测试用例问题不是不大的,这里最有用的部分是copilot能给测试用例取名,解决了tdd的一个巨大痛点。
- copilot会生成一些无用的用例,所以好的测试思维很重要,无效的用例就需要跳过;
- copilot断言能力有限,本质就是copilot无法从用例中推理出需要测试的点,这应该是模型的能力有限,后面更高的大语言模型版本可能会解决这个问题;
- 实现的代码可能还是要自己写,然而copilot会帮我们补全代码,所以工作量是可控的;
- 重构可能还是要靠自己,其实本质上就是copilot会写代码,但不一定会写出好的代码,重构其实并没有让功能变得更强,只是让代码变得更好维护,不过大语言模型如何去理解好维护这个概念,目前还不是特别清楚。