使用生成式AI编写代码的正确方法

使用生成式AI编写代码的正确方法

你多久使用一次生成AI来编写代码?那么编写测试呢?你可能做反了。

单元测试是最糟糕的。不要误解我的意思,它们是CI流水线或自动化的宝贵组成部分。但是编写它们真是一件令人头疼的事情。

我认识的大多数开发人员,包括我自己,都喜欢直接开始编写代码,并乐于在功能完成后立即交付。但正如我们这些曾经编写过需要在生产环境中支持的内容的人所知道的那样,这是不可行的。

不幸的是,单元测试、集成测试和端到端测试都需要编写,以确保你的代码完全满足业务问题,涵盖所有边界情况,并设置为保持向后兼容性。事实上,它非常重要,以至于有一个名为测试驱动开发(TDD)的完整编码范式https://testdriven.io/test-driven-development/

测试驱动开发(TDD)的前提是在编写任何代码之前编写所有单元测试。如果你完整地构建单元测试,确保覆盖了所有的业务需求,那么你可以边编写代码边进行测试。在编写代码时,你的单元测试将停止失败,确保你按照预期完成了任务。这种方式确保了代码的正确性和可靠性。

几年前,当我还是开发经理时,我认为这是一个很棒的想法。在我之上的高级经理和主管喜欢听我的团队编写了数百个单元测试,以及我们如何为测试划分优先级以保持高质量。我向其他团队介绍了TDD,并展示了我的团队是如何成功使用它的。如果你想了解更多关于编程的相关内容,可以阅读以下这些文章:
Meta正在做上帝的工作:向世界发布令人震惊的优秀编程模型!
畅销编程书籍中的10个编码秘密
Mojo:比Python快35000倍的AI编程语言
作为一个数据科学家/分析师,不要重复这5个编程错误

但我要告诉你一个秘密。我们先写代码。是的,我们当时并没有先编写测试,然后逐步观察测试成功与否,而是先编写代码,然后再编写测试。更糟糕的是,我们在合并到主分支时会压缩提交记录,这样就没有人能够察觉到。

回想起来,除了有点滑稽之外,这其实很可疑。我后悔让这种做法离我们远去。我们仍然有很多单元测试,但绝对不是真正的TDD。

跳过几年的时间,来到今天,我变得更糟糕了。我不再管理开发团队,但我写了很多代码。但是现在我会先写代码,然后让ChatGPT为我生成测试。我很高兴,因为至少代码有了测试。任何测试都比没有测试好。

直到Matt Carey在我的播客上谈论生成AI时,我才意识到我完全颠倒了。

生成式AI很棒,因为它会按照你的要求执行任务。但另一方面,生成式AI也很糟糕,因为它会按照你的要求执行任务。

如果你为API端点编写了一个处理程序,其中有一个错误,然后要求ChatGPT为它编写单元测试,猜猜你最终会得到什么?是的,你将得到一个断言了存在错误行为的单元测试。

你发布了代码,单元测试通过,它进入了生产环境。三天后,你坐在你的电脑前,收到了一张描述bug的Jira票据。现在你必须返回,修复问题,更新单元测试,并希望你没有错过任何其他内容。

将代码交给ChatGPT并不会为其提供业务上下文。生成式AI在上下文中表现出色。你提供的信息越多,也就是上下文,你的结果就越好。但是如果你向ChatGPT提供用户故事和有错误的代码,然后要求其编写单元测试,你可能会得到不同的结果。有时你可能会得到一个回复,告诉你代码不符合要求。而其他时候,你可能会得到你要求的东西——有错误的代码的单元测试。

但如果我们把这个过程颠倒过来呢?如果我们使用生成AI作为编写TDD代码的部分会怎么样?

尽管我不喜欢编写单元测试,但测试的作用不可忽视。单元测试的目的是保证代码特定部分的行为。这个行为是在用户故事中定义的。

让我们看一个简单的火车票预订系统的用户故事示例。

作为一个顾客,我想要一个简单的方法来预订火车票,让我能够选择日期和所需座位数。

验收标准

  • 我只能预订未来的火车票
  • 我可以预订当天的车票
  • 我必须预订1到8张票(不能使用字母字符)
  • 如果没有足够的票可用,我会收到一个错误消息,提示选择一个不同的日期

从这个用户场景中,你可以推断出需要构建的相当多的验证。这些验证应该都有相应的单元测试。举几个例子,在我们上面的故事中,我们将对以下内容进行单元测试:

  • 成功预订
  • 当用户预订过去的日期时失败
  • 当用户预订0张票时失败
  • 当用户预定9张票时失败
  • 当用户预定8张票时成功

你可以看出我要表达的意思。你可以构建几十个单元测试来验证边界、边缘情况和正常路径的行为。

当你实践测试驱动的开发时,首先编写所有这些单元测试,详细描述了故事的每个业务需求。

单元测试也倾向于假设。在编写测试时,假定了一些实现细节。例如,如果你的数据存储在DynamoDB中,你的单元测试将需要模拟对AWS SDK的调用。

不仅如此,你的测试还断言数据的形状。你应该在失败和成功的场景中验证函数的所有输入和输出。

考虑到这一点,让我们来谈谈生成人工智能的作用。

现在我们明白了单元测试如何明确定义业务需求,断言请求和响应的模式,并包含对底层实现的假设,那么下一步就很明显了——让我们把它交给ChatGPT吧!

你的单元测试应该是生成式AI编写代码所需的所有上下文。下面是一个用Node.js编写的项目提示符示例:

以下是一个满足所有以下单元测试的Node.js函数处理程序,要求在性能方面尽可能高效,但仍然易于长期维护。

请将满足所有单元测试的源代码保存到磁盘上。

但这还不够!现在你有了代码,是时候充分利用这些单元测试了。立即运行它们并评估代码。如果所有单元测试都通过了,那就完成了!如果你遇到了失败,那就开始施展你的魔法吧。

你的单元测试应该已经生成了一组运行成功和失败的结果。过滤这些结果,只留下失败的部分,并将其反馈给AI,确保完整的对话历史完好无损。通过提供完整的对话历史记录,你可以向AI提供单元测试的上下文,它之前编写的代码,以及有效和无效的内容。

在迭代代码时,此上下文非常有价值。生成式AI使用它来迭代并从以前的尝试中吸取教训。它可以看到它尝试了什么,单元测试的结果,以及在再次运行单元测试时对代码所做的更新如何导致不同(或相同)的结果。

所以这个循环变成了生成代码,将代码保存到磁盘,然后运行单元测试。如果单元测试失败,将结果反馈给生成代码步骤,以便在下一次迭代中进行调整。

如果你想看看实际情况,我构建了一个用于生成Node.js代码的概念证明,你可以尝试一下https://github.com/allenheltondev/tdd-ai

这听起来很酷,但也存在一些限制。它并不完美。你需要在某些地方进行一些更改。

除非你为AI提供风格指南或参考,否则它不会以你的风格编写——它可能无法通过测试规则。你还需要进行检查并参数化某些值,如数据库表名,连接字符串,调用的资源等…

从实用的角度考虑,AI并不总是能够编写出满足你的单元测试的代码。因此,你需要实现一个断路器,在尝试一定次数后停止AI的尝试。这将减少你的反馈循环,也为你节省很多钱。

说到钱,我们得谈谈价格。这是一种权衡。在我早期的测试中,GPT-4在编写代码和合理化来自单元测试的需求方面做得更好。GPT-3从来没有完全满足所有的单元测试,但GPT-4在1或2次尝试中就做到了。

在定价方面,GPT-4的价格是它的两倍,为每1,000个输入标记收费0.03美元,每1,000个输出标记收费0.06美元。费用将根据你拥有的单元测试数量和完成这些测试所需的代码长度而大幅变化。

在我提供的示例中,我有5个描述我想要编写的函数的单元测试。这些测试加上命令消耗了941个标记。生成的输出为353个标记。

如果我需要修改代码,我会将整个对话历史和一个额外的注释传递进去,描述我希望OpenAI做什么。这意味着第二次迭代的提示标记是941+350+50(50为额外的注释),生成的输出大约又是350个标记。

因此,由于你提供的对话上下文,每次迭代提示标记都会累积。所以如果你迭代5次,你将消耗大约10.5K个标记。按照每1,000个标记0.03美元的价格,5次迭代的单次运行成本约为0.31美元。

如果你每天使用这个方法几十次,那么费用可能会累积起来。假设你每天运行50次,一个月中的20天。每次运行平均迭代5次。计算如下:

50 runs x 20 days x $0.31 per run = $310/month

现在,根据你的时薪,这似乎是值得投资的。如果你每次运行都能节省30分钟的编码时间,那就意味着你将在一个月内获得500个小时的开发时间!这一计算结果平均每小时只需支付0.62美元的“免费”开发时间。对我来说,这似乎是一笔非常划算的交易!

抛开所有的优点和缺点,这并不是完美的。如果我们能够让AI以80%的完成率编写代码,并让它满足我们所有的单元测试,那么我们就大大提高了开发人员的工作效率。我们并不打算取代开发者。我们希望让它们更快。将繁重的工作交给AI,让他们能够专注于业务问题。

编写单元测试,专注于完全满足业务需求,让人工智能来做繁重的工作,然后由开发人员继续完善生成的代码,获得收益。

编码快乐!感谢阅读!你还可以订阅我们的YouTube频道,观看大量大数据行业相关公开课:https://www.youtube.com/channel/UCa8NLpvi70mHVsW4J_x9OeQ;在LinkedIn上关注我们,扩展你的人际网络!https://www.linkedin.com/company/dataapplab/

原文作者:Allen Helton
翻译作者:Dou
美工编辑:过儿
校对审稿:Jason
原文链接:https://betterprogramming.pub/test-driven-development-with-ai-the-right-way-to-code-using-generative-ai-be242dbf8f5a