news 2026/11/3 23:43:45

Behavior-Driven AI-TDD:大模型时代研发闭环的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Behavior-Driven AI-TDD:大模型时代研发闭环的工程实践

1. 为什么传统开发工作流在大模型时代开始失灵

过去十年,我们做软件研发基本遵循一套相对固定的节奏:需求评审、技术方案设计、编码、单元测试、集成测试、验收测试、上线。这套流程在“人写代码、人写测试、人做验收”的前提下运转得还算顺畅。但从2024年开始,我陆续参与了几个把大模型能力嵌入研发链路的项目,发现一个很现实的问题:当代码的产出方从纯人类变成“人类+大模型”之后,原来那套以人工评审和人工测试为核心的闭环,开始出现明显的裂缝。

裂缝体现在三个地方。第一,大模型生成代码的速度远超人工评审的速度,评审环节直接变成瓶颈,很多团队干脆跳过评审直接合并,质量隐患被埋进去。第二,传统单元测试是“人根据实现写断言”,而大模型生成的实现本身带有不确定性,同一段提示词两次生成的代码结构可能不同,写死的断言很容易失效。第三,验收标准如果还是用自然语言写在需求文档里,人和大模型对同一句话的理解可能完全不一样,最后交付出来的东西“看起来对,用起来不对”。

这就是 Behavior-Driven AI-TDD 这个方向被提出来的现实背景。它不是凭空造概念,而是被工程实践逼出来的。Behavior-Driven 解决的是“验收标准怎么变成可执行的东西”,AI-TDD 解决的是“测试怎么跟上大模型生成代码的节奏”,两者合在一起,才构成大模型时代真正能跑通的研发闭环。

我先把结论放在前面:这套方法论的核心不是让大模型去写测试,而是让行为描述成为人和大模型之间的共同契约,测试只是这个契约的执行载体。理解这一点,后面的所有工程细节才有落脚点。

2. Behavior-Driven AI-TDD 的整体设计与思路拆解

2.1 从 TDD 到 AI-TDD,变的是什么

经典 TDD 的红绿重构循环大家都很熟:先写一个失败的测试,再写刚好能让它通过的实现,最后重构。这个循环成立的前提是“测试的编写者”和“实现的编写者”是同一个人,或者至少是同一个认知水平的人。当实现由大模型生成时,这个前提被打破了。

AI-TDD 的变化在于,测试不再只是“验证工具”,而是变成了“提示词的一部分”。你给大模型的输入里,除了功能描述,还应该包含可执行的验收测试。大模型在生成实现的时候,实际上是在“看着测试写代码”。这带来一个很直接的好处:生成出来的代码天然就是朝着通过测试去的,减少了大量来回修改。

但这里有个坑我踩过。如果你直接把传统单元测试丢给大模型,它很容易生成“针对测试写死”的代码,比如硬编码返回值来骗过断言。所以 AI-TDD 里的测试必须是行为级别的测试,而不是实现级别的测试。行为级别测试关注的是“输入什么、输出什么、状态怎么变”,不关心内部用了什么数据结构、调了什么函数。这样大模型就没法作弊,只能老老实实实现逻辑。

2.2 Behavior-Driven 在这里扮演什么角色

Behavior-Driven Development 的核心是用自然语言描述行为,通常是 Given-When-Then 格式。这个格式在大模型时代突然变得极其有价值,因为它同时满足三个条件:人类能读懂、大模型能理解、可以机械地转换成可执行测试。

我举个实际例子。假设我们要做一个订单折扣计算模块。传统需求文档可能写“VIP用户下单满100减20”。这句话人和大模型都能理解,但理解可能不一致:VIP是等级还是标签?满100是含税还是不含税?减20是减总价还是减单价?这些歧义在人工开发时靠沟通解决,在大模型开发时就会变成随机错误。

用 Given-When-Then 重写:

Given 一个等级为VIP的用户 And 购物车商品总价为120元(不含税) When 用户提交订单 Then 订单应付金额应为100元

这个描述没有歧义,而且可以直接映射成测试用例。大模型拿到这个描述,生成的实现基本不会跑偏。这就是 Behavior-Driven 在 AI-TDD 里的真正作用:把模糊需求压缩成无歧义契约。

2.3 整体架构的分层设计

我在实际项目中把这套东西拆成四层,从下到上分别是:

层级名称职责主要产出
L1行为描述层用 Given-When-Then 写清楚每个功能的行为.feature 文件或结构化 YAML
L2测试生成层把行为描述转成可执行测试测试代码或测试配置
L3实现生成层大模型根据行为描述和测试生成实现业务代码
L4验证反馈层跑测试、收集失败信息、回喂给大模型修复后的代码

这个分层的关键在于 L2 和 L3 是解耦的。测试生成不依赖具体实现,实现生成必须通过测试。这样即使大模型换了、提示词改了,只要行为描述不变,最终交付的行为就是稳定的。

注意:不要试图让大模型同时生成测试和实现。我试过,结果是大模型会生成“刚好互相匹配”的测试和实现,测试覆盖不到边界情况,等于白做。测试必须独立于实现生成。

3. 核心细节解析与实操要点

3.1 行为描述怎么写才不会被大模型误解

Given-When-Then 看起来简单,但写得好不好直接决定整套流程能不能跑通。我总结了几个实操中验证过的规则。

第一条,Given 里只放状态,不放动作。比如“Given 用户已登录”是状态,“Given 用户点击登录按钮”是动作,后者应该放到 When 里。混在一起会让大模型分不清前置条件和触发条件。

第二条,When 里只放一个动作。一个场景只触发一件事。如果你写“When 用户提交订单并支付”,大模型可能会生成一个把提交和支付耦合在一起的实现,后面想单独测试提交逻辑就没办法了。

第三条,Then 里只断言可观测结果。可观测结果包括返回值、状态变更、外部调用。不要断言内部变量,比如“Then 订单对象的 _discountApplied 字段为 true”,这是实现细节,大模型换个写法就失效了。

第四条,每个场景的用词必须一致。同一个概念在不同场景里要用同一个词。比如前面用“购物车”,后面就不能用“购物篮”。大模型对同义词的容忍度比人类低,用词不一致会导致它认为是两个不同的东西。

我整理了一个对照表,左边是容易出问题的写法,右边是修正后的写法:

问题写法问题所在修正写法
Given 用户是VIP且已登录两个状态混在一起Given 用户等级为VIP / And 用户已登录
When 系统计算折扣动作主体是系统,不是用户When 用户提交订单
Then 折扣逻辑正确执行不可观测Then 订单应付金额为100元
Given 购物车有商品数量不明确Given 购物车有3件商品,总价120元

3.2 测试生成层的实现方式选择

把行为描述转成可执行测试,有三条路可以走,我分别试过,各有适用场景。

第一条路是模板映射。预先定义好 Given-When-Then 到测试代码的映射规则,用脚本生成。优点是稳定、可控、不依赖大模型。缺点是灵活性差,遇到复杂场景模板覆盖不到。适合行为模式固定的业务,比如 CRUD 接口测试。

第二条路是大模型生成测试。把行为描述喂给大模型,让它生成测试代码。优点是灵活,能处理复杂场景。缺点是不稳定,同一段描述两次生成的测试可能不一样。适合探索阶段,快速验证行为描述有没有歧义。

第三条路是混合模式。简单场景用模板,复杂场景用大模型生成,生成后人工审核一次,审核通过的测试固化下来。这是我在生产环境用得最多的方式。审核这一步不能省,因为大模型生成的测试偶尔会漏掉边界条件。

具体操作上,我会给大模型一个固定的测试框架模板作为上下文,比如:

# 测试框架模板 def test_{scenario_name}(): # Given {setup_code} # When {action_code} # Then {assertion_code}

然后让大模型只填充 setup_code、action_code、assertion_code 三部分。这样生成的测试结构统一,后续维护成本低。

3.3 实现生成层的提示词工程

实现生成层的提示词设计是整套流程里最需要打磨的部分。我现在的提示词结构是这样的:

你是一个后端开发工程师。请根据以下行为描述和测试用例,实现对应的业务逻辑。 行为描述: {feature_description} 测试用例: {test_code} 约束条件: 1. 不要修改测试用例 2. 不要硬编码返回值来通过测试 3. 实现要能通过所有测试用例 4. 使用 {language} 和 {framework} 请输出完整的实现代码。

这里有几个细节值得展开。约束条件第一条“不要修改测试用例”很重要,因为大模型有时候会“聪明”地改测试来让它通过。第二条“不要硬编码”是防止它作弊。第三条是目标。第四条限定技术栈,避免它生成不兼容的代码。

还有一个我踩过的坑:不要把整个项目的代码都塞进上下文。一开始我想让大模型了解项目全貌,把几十个文件都放进去,结果它生成的代码风格混乱,还经常引用不存在的模块。后来我改成只放直接相关的接口定义和数据结构,生成质量明显提升。

3.4 验证反馈层的闭环设计

测试跑失败之后,失败信息怎么回喂给大模型,决定了修复效率。我的做法是把失败信息结构化之后再回喂,而不是直接把原始报错丢过去。

结构化后的失败信息包含四部分:失败的测试名称、期望值、实际值、失败位置。比如:

失败测试:test_vip_order_discount 期望:订单应付金额为100元 实际:订单应付金额为120元 失败位置:discount_service.py 第23行

大模型拿到这个结构化信息,修复准确率比拿到原始堆栈高很多。原始堆栈里有很多噪音,大模型容易被带偏。

另外,修复循环要设上限。我一般设3次,3次还修不好就说明行为描述或者测试本身有问题,需要人工介入。无限循环修复不仅浪费token,还可能让大模型把代码改得越来越乱。

4. 实操过程与核心环节实现

4.1 环境准备与技术栈选择

这套流程对技术栈没有硬性要求,但有几个选择会影响体验。我目前用的组合是 Python + pytest + Behave(做行为描述解析)+ 一个大模型API。选 Python 是因为生态成熟,pytest 的断言信息足够结构化,Behave 对 Given-When-Then 的支持最自然。

如果你用 Java,可以用 JUnit + Cucumber。如果用 JavaScript,可以用 Jest + Cucumber.js。核心是行为描述解析器和测试框架要能配合。

大模型的选择上,我建议用支持长上下文和结构化输出的。因为行为描述和测试代码加起来可能几千token,上下文太短会截断。结构化输出能力指的是它能稳定按你要求的格式返回代码,而不是夹带一堆解释文字。

4.2 从零搭建一个最小可运行示例

我拿一个真实的折扣计算场景来演示。假设我们要实现一个订单折扣模块,规则是:VIP用户满100减20,普通用户满200减10。

第一步,写行为描述文件discount.feature:

Feature: 订单折扣计算 Scenario: VIP用户满100减20 Given 用户等级为VIP And 购物车商品总价为120元 When 用户提交订单 Then 订单应付金额为100元 Scenario: VIP用户不满100不打折 Given 用户等级为VIP And 购物车商品总价为80元 When 用户提交订单 Then 订单应付金额为80元 Scenario: 普通用户满200减10 Given 用户等级为普通 And 购物车商品总价为250元 When 用户提交订单 Then 订单应付金额为240元

第二步,用 Behave 的步骤定义把 Given-When-Then 映射到测试代码。这一步可以手写,也可以让大模型生成后审核。我手写一遍给你看:

from behave import given, when, then from discount_service import calculate_discount @given('用户等级为{level}') def step_set_user_level(context, level): context.user_level = level @given('购物车商品总价为{amount:d}元') def step_set_cart_amount(context, amount): context.cart_amount = amount @when('用户提交订单') def step_submit_order(context): context.final_amount = calculate_discount( context.user_level, context.cart_amount ) @then('订单应付金额为{expected:d}元') def step_check_amount(context, expected): assert context.final_amount == expected, \ f"期望{expected}元,实际{context.final_amount}元"

第三步,让大模型根据行为描述和测试生成discount_service.py的实现。提示词按前面说的结构组织。生成结果大概是:

def calculate_discount(user_level, cart_amount): if user_level == 'VIP': if cart_amount >= 100: return cart_amount - 20 return cart_amount else: if cart_amount >= 200: return cart_amount - 10 return cart_amount

第四步,跑测试。behave命令执行,三个场景全过。如果没过,把失败信息结构化后回喂给大模型修复。

这个示例很小,但完整跑通了 L1 到 L4 四层。你可以把场景换成你实际业务里的逻辑,流程完全一样。

4.3 参数计算与边界条件的处理

上面的示例有个隐藏问题:边界条件没覆盖。VIP用户刚好100元打不打折?普通用户刚好200元打不打折?这些边界如果不写进行为描述,大模型生成的实现可能对也可能错,全靠运气。

我的做法是每个数值条件都补一个边界场景。上面的例子应该补两个:

Scenario: VIP用户刚好100元享受折扣 Given 用户等级为VIP And 购物车商品总价为100元 When 用户提交订单 Then 订单应付金额为80元 Scenario: 普通用户刚好200元享受折扣 Given 用户等级为普通 And 购物车商品总价为200元 When 用户提交订单 Then 订单应付金额为190元

补完之后再让大模型生成实现,它就会正确处理>=而不是>。这个细节在人工开发时靠 code review 发现,在大模型开发时靠行为描述覆盖。

4.4 把流程接入现有研发链路

这套东西不能孤立跑,得接入现有的 CI/CD。我的做法是在 CI 里加一个阶段:行为测试阶段。每次代码提交,先跑行为测试,通过了才进入后续的单元测试和集成测试。

具体配置上,以 GitHub Actions 为例:

- name: Run Behavior Tests run: | behave features/ env: MODEL_API_KEY: ${{ secrets.MODEL_API_KEY }}

如果行为测试失败,CI 直接挂掉,不允许合并。这样行为描述就成了真正的契约,而不是文档摆设。

还有一个实践:行为描述文件纳入版本控制,和代码一起 review。行为描述的变更要走 PR 流程,因为改行为描述等于改契约,影响范围可能很大。

5. 常见问题与排查技巧实录

5.1 大模型生成的实现总是差一点通过测试

这是最常见的问题。表现是测试跑失败,失败信息显示实际值和期望值很接近但不等。原因通常是行为描述里的某个条件没写清楚,大模型做了合理但错误的假设。

排查思路:先看失败的测试是哪个场景,然后检查这个场景的 Given 里有没有遗漏的状态。比如折扣场景里如果没写“不含税”,大模型可能按含税算,结果差一个税率。

解决技巧:在行为描述里把所有隐含假设显式写出来。宁可写啰嗦,不要留空白。我现在的习惯是每个场景至少检查一遍:用户状态、数据状态、时间状态、环境状态,这四类状态有没有遗漏。

5.2 测试通过但实际功能不对

这个问题更隐蔽。测试全绿,但上线后发现功能不对。原因通常是行为描述本身写错了,或者测试断言太弱。

排查思路:拿实际业务场景去对照行为描述,看有没有场景没覆盖到。比如折扣场景里如果只写了VIP和普通用户,没写“新用户首单”这种特殊情况,那新用户下单就会走到普通用户分支,可能不符合业务预期。

解决技巧:行为描述写完之后,让业务方读一遍,确认没有遗漏。技术方读和业务方读关注点不一样,业务方能发现技术方忽略的业务规则。

5.3 大模型修复循环不收敛

表现是修复了3次还是失败,而且每次失败的原因都不一样。这通常说明行为描述内部有矛盾,或者测试之间有冲突。

排查思路:把所有失败信息放在一起看,找共同点。如果不同测试对同一个行为有不同的期望,那就是行为描述矛盾了。

解决技巧:把行为描述拆细,一个场景只验证一个行为。不要在一个场景里验证多个相互关联的行为,那样失败时很难定位是哪个行为出了问题。

5.4 常见问题速查表

问题现象可能原因排查方向解决技巧
实现差一点通过行为描述有隐含假设检查Given是否遗漏状态显式写出所有假设
测试通过功能不对行为描述覆盖不全对照业务场景查遗漏让业务方审核描述
修复循环不收敛行为描述内部矛盾对比不同测试的期望拆分场景,一个场景一个行为
大模型改测试作弊提示词约束不够检查提示词约束条件明确禁止修改测试
生成代码风格混乱上下文塞太多检查提示词上下文只放直接相关的接口定义
测试生成不稳定用了大模型生成测试检查测试生成方式简单场景用模板,复杂场景生成后固化

5.5 几个我踩过的坑

第一个坑是行为描述写得太技术化。一开始我把数据库表名、字段名写进 Given 里,结果大模型生成的实现直接操作数据库,完全绕过了业务层。后来改成用业务语言描述,比如“购物车商品总价”而不是“cart_table.total_amount”,生成质量就好了。

第二个坑是测试断言太宽松。有次我写“Then 订单状态为已完成”,大模型生成的实现把所有订单都标成已完成,测试也过了。后来改成“Then 订单状态为已完成 And 订单应付金额为100元”,才把逻辑约束住。

第三个坑是忽略了大模型的上下文长度限制。有次行为描述文件写了几百行,超出上下文限制被截断,大模型只看到前半部分,生成的实现自然不对。后来我把行为描述按功能模块拆分,每个模块单独生成,问题解决。

第四个坑是没有版本控制行为描述。有次改了行为描述但没记录,后来发现功能不对,想回滚都不知道回滚到哪个版本。现在行为描述和代码一样走 Git,每次变更都有记录。

6. 这套方法论适合什么团队、什么场景

不是所有团队都适合上这套东西。我观察下来,适合的团队有几个特征:业务逻辑相对复杂、需求变更频繁、已经有一定测试基础、团队愿意接受大模型作为研发参与者。

不适合的场景也很明显:一次性脚本、原型验证、业务逻辑极简的项目。这些场景上 Behavior-Driven AI-TDD 是杀鸡用牛刀,投入产出比不划算。

从场景上看,最适合的是业务规则密集的后端服务,比如订单、支付、风控、计费。这些场景行为描述写清楚之后,大模型生成的实现质量很高,而且行为描述本身就是很好的业务文档。

前端场景也可以用,但要注意 UI 行为描述比后端难写。后端的 Given-When-Then 很自然,前端的“用户看到什么”比较难精确描述。我目前在前端场景还是以人工测试为主,行为描述作为辅助。

我个人在实际操作中的体会是,这套方法论最大的价值不是省了多少写代码的时间,而是把模糊需求逼成了精确契约。以前很多需求歧义要到测试阶段甚至上线后才暴露,现在在写行为描述的时候就暴露了。这个价值比省代码时间大得多。

最后分享一个小技巧:行为描述文件不要一次写全,按迭代增量写。每个迭代只写这个迭代要做的行为,做完再写下个迭代的。一次写全容易写成大而全的文档,反而没人维护。增量写、增量跑、增量固化,这套流程才能持续运转起来。

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

冗余局部变量警告:原理、识别与工程化治理

1. 这个警告不是Bug,是编辑器在提醒你“代码变胖了”你有没有在写完一段逻辑清晰的代码后,突然被编辑器用黄色波浪线温柔地戳一下——弹出一句:“Local variable ‘xxx’ is redundant. No longer valid.”?紧接着变量名还被自动灰…

作者头像 李华
网站建设 2026/11/3 23:42:57

显存不足别急着换卡:从排查到优化的实战指南

1. 显存不足的表现远不止"out of memory"这一种1.1 CUDA OOM是最典型的报错,但未必是你第一时间看到的先聊最直白的场景。你在跑PyTorch训练脚本,或者加载一个本地大模型的时候,控制台突然弹出这段经典的红色报错:Runti…

作者头像 李华
网站建设 2026/11/3 23:42:16

AWS EKS GPU服务部署与Karpenter自动伸缩实战

1. EKS GPU 服务部署实战指南在云计算领域,GPU加速服务已经成为AI推理、图像处理和语音转录等场景的标配。AWS EKS作为托管Kubernetes服务,结合Karpenter的自动节点管理能力,可以构建高弹性、低成本的GPU服务架构。本文将分享我在实际项目中部…

作者头像 李华
网站建设 2026/11/3 23:41:11

Prometheus告警规则模板库:从零搭建到生产调优实战

1. 为什么我坚持维护一套自己的告警规则模板库做运维和SRE这些年,我经手过的监控系统少说也有十几套了。每次新环境搭建、每次迁移、每次接手别人的集群,最耗时间的事情往往不是装Prometheus、不是配Grafana,而是告警规则到底该怎么写。装软件…

作者头像 李华
网站建设 2026/11/3 23:38:44

从NumPy到Transformer:程序员学LLM必须补的数学工具与Shape思维

1. 从一行 NumPy 说起:为什么程序员学 LLM 要回头补数学工具很多人第一次接触大模型,脑子里想的都是 Transformer、注意力机制、Token、微调这些词,恨不得当天就把一个开源模型跑起来。但真到动手的时候,卡住你的往往不是模型本身…

作者头像 李华
网站建设 2026/11/3 23:35:52

油气勘探软件成本透明化系统设计与实践

1. 项目背景与核心价值在油气勘探开发领域,Petrel作为行业领先的地质建模与油藏模拟软件,其高昂的使用成本一直是企业IT预算的重要组成部分。过去三年间,我们注意到一个普遍现象:业务部门对软件使用效益缺乏直观认知,导…

作者头像 李华