動AI-TDD:讓大模型重塑軟件研發(fā)閉環(huán)的工程實踐)
上個月我把項目里一個耦合嚴(yán)重的支付模塊完整走了一遍 Behavior-Driven AI-TDD 重構(gòu)。整個過程最讓我意外的不是大模型寫測試寫得有多快而是我把測試從“補(bǔ)丁”重新變成“設(shè)計工具”之后需求溝通、任務(wù)拆分、代碼審查甚至跨端協(xié)作的方式全都跟著變了。今天這篇就來聊聊在大模型時代為什么行為驅(qū)動和 AI 輔助測試開發(fā)能形成一個真正的軟件研發(fā)閉環(huán)以及從工作流重構(gòu)到工程實踐這條路上哪些思路可以直接抄作業(yè)哪些坑我已經(jīng)替你踩過了。1. 傳統(tǒng) TDD 卡在哪里寫完測試就沒了下一步1.1 一項看似美好的紀(jì)律為什么難落地TDD 的紅綠重構(gòu)循環(huán)理論上非常自洽先寫一個失敗的測試讓它驅(qū)動出最小實現(xiàn)再在綠燈保護(hù)下重構(gòu)。任何有幾年經(jīng)驗的人都知道這個循環(huán)的價值但真讓團(tuán)隊長期堅持下來失敗率極高。問題不在態(tài)度而在成本。傳統(tǒng) TDD 對開發(fā)者的領(lǐng)域思考能力要求太高。寫第一個測試之前你必須把需求拆成可驗證的行為想清楚輸入、輸出、邊界和異常分支。這本身就是一份高質(zhì)量設(shè)計文檔的思考量。大多數(shù)業(yè)務(wù)模塊留給寫測試的時間往往比實現(xiàn)代碼還緊。于是大家會走一條大家都走過的捷徑先寫實現(xiàn)再補(bǔ)一層“看起來會跑”的測試最后用覆蓋率數(shù)字安慰自己。第二重阻力來自反饋速度。老式 TDD 提倡頻繁切換紅燈和綠燈但如果你要打開瀏覽器、啟動外圍服務(wù)、準(zhǔn)備數(shù)據(jù)庫數(shù)據(jù)才能驗證一個行為幾分鐘的反饋周期足以把人的心流徹底打碎。慢反饋會讓人下意識少跑測試、少寫測試最后退化成“只在提測前補(bǔ)幾個用例”。大模型恰好有能力把這兩重阻力的水位降下來。它可以快速生成測試骨架、補(bǔ)全邊界用例、甚至把 Gherkin 場景變成可執(zhí)行測試代碼。但這里有個關(guān)鍵前提如果需求本身是模糊的AI 生成的測試也只能是“對代碼現(xiàn)有行為的影像學(xué)復(fù)制”而不是對業(yè)務(wù)行為的約束。1.2 需求歧義測試本身的“測試”誰來寫傳統(tǒng) TDD 的第一個紅依賴開發(fā)者對需求的理解。然而大多數(shù)團(tuán)隊的需求來源是 PRD、口頭要求或者一段即時消息真正的驗收標(biāo)準(zhǔn)要靠人腦“腦補(bǔ)”。比如“訂單金額滿 200 打八折”這里面沒有說清楚是否包含運費、是否限制會員、折扣是否疊加。如果你直接讓大模型寫test_discount()它可以寫出十個不同版本的斷言每一個都能自圓其說但哪一個才代表業(yè)務(wù)這正是 TDD 循環(huán)斷裂的根源測試一旦基于開發(fā)者的主觀理解來寫它就不是約束而是把開發(fā)者的假設(shè)固化成了機(jī)器可執(zhí)行的規(guī)則。等測試變綠你以為驗證了需求實際只是驗證了當(dāng)時的猜測。更麻煩的是這種隱性決策會一路傳導(dǎo)到線上直到某個用戶場景觸發(fā)歧義你才會發(fā)現(xiàn)寫測試的人當(dāng)時替業(yè)務(wù)做了一個不該做的決定。所以在引入 AI-TDD 之前首要問題不是“怎么讓大模型寫測試”而是“用什么形式把需求意圖變成模型和人都不產(chǎn)生歧義的規(guī)范”。在我的實踐里答案就是 Behavior-Driven Development 里的行為描述。2. Behavior-Driven 真正給 AI-TDD 提供了什么需求與代碼之間的語義錨點2.1 用 Gherkin 場景把人類意圖變成機(jī)器可讀的驗收標(biāo)準(zhǔn)Behavior-Driven 的核心不是某款測試框架而是用 Given-When-Then 這樣的半結(jié)構(gòu)化語言把業(yè)務(wù)行為描述成“前置條件 觸發(fā)動作 可觀察結(jié)果”。最常用的載體是 Gherkin 語法。Feature: 訂單折扣 Scenario: 會員訂單金額滿足閾值后自動打折 Given 用戶是會員 And 當(dāng)前訂單金額為 200 元 When 用戶提交訂單 Then 系統(tǒng)應(yīng)用 8 折優(yōu)惠 And 最終支付金額為 160 元這段描述沒有任何代碼語法業(yè)務(wù)人員能看懂測試人員能據(jù)此寫用例開發(fā)人員能據(jù)此做設(shè)計大模型也能據(jù)此理解驗收標(biāo)準(zhǔn)。關(guān)鍵是它把“打八折”這種模糊意圖拆成了可以直接驗證的數(shù)值和結(jié)果。AI 拿到這類輸入時不會自由發(fā)揮因為每個 Given 都是狀態(tài)準(zhǔn)備每個 Then 都是斷言目標(biāo)。在實踐中一個 Feature 文件里通常會有主流程場景、異常場景、邊界場景。把這些場景逐條喂給大模型它會自動補(bǔ)全測試數(shù)據(jù)、選擇測試金字塔的層級甚至幫你發(fā)現(xiàn)遺漏的臨界條件。這比讓模型直接看一個類名要可靠得多。2.2 為什么 AI 生成測試需要“行為上下文”而不是一句注釋我拿同樣一個函數(shù)做過對比。一種方式是給大模型一句“給 getDiscount 寫測試”它大概率會生成一個 happy path 用例斷言輸入金額和折扣比例測試通過。另一種方式是給一段訂單場景要求它從用戶提交訂單的角度生成測試。此時它會關(guān)心訂單狀態(tài)、會員類型、金額閾值、優(yōu)惠疊加甚至?xí)选爸Ц督痤~精確到分”作為顯式斷言寫進(jìn)去。兩者最大的差別在語義錨點的數(shù)量級。大模型是概率生成器上下文越具體輸出的隨機(jī)性越小上下文越模糊它越會往“平均化”的方向靠。行為上下文本身就是約束器。你把這幾個 Given/When/Then 步驟作為錨點LLM 生成的代碼就不太容易跑題而且生成的測試天然具備業(yè)務(wù)可讀性。另一個容易被忽略的收益是可追溯性。Gherkin 場景寫上需求編號和作者生成的測試代碼通過注釋或命名約定關(guān)聯(lián)回去。將來需求變更時你能直接定位哪些測試需要調(diào)整而不是靠全文搜索某個方法名。2.3 Feature 文件的精益化從文檔到測試骨架很多團(tuán)隊引入 BDD 時會把 Feature 文件當(dāng)成寫給人看的文檔只在下游手工翻譯成測試。這種斷裂會讓 Feature 文件和實際代碼很快脫節(jié)。在 AI-TDD 工作流里Feature 文件應(yīng)該是所有測試生成的真源頭不能允許它成為觀賞性文檔。我在項目里會把 Feature 文件的解析做成一個小流水線先用工具讀取 Gherkin 場景提取步驟文本再交給大模型生成測試代碼骨架。生成好的測試文件直接放到測試目錄跑一遍測試收集器確認(rèn)用例數(shù)量和場景數(shù)量匹配。FitNesse 時代做這件事需要大量膠水代碼現(xiàn)在大模型可以直接輸出 pytest-bdd 風(fēng)格或 SpecFlow 風(fēng)格的測試類替人省掉了繁重的 Step Definition 編寫。這里有一個實操提醒不要讓自動化流水線一開始就做得太重。先用最樸素的“手動跑一遍 Gherkin → 復(fù)制給模型 → 粘貼產(chǎn)物”的方式驗證流程等穩(wěn)定了再把它封裝成 CLI 工具。一上來就搞平臺化很容易被工具本身的維護(hù)成本拖垮。3. 重構(gòu)后的研發(fā)閉環(huán)紅-綠-重構(gòu)在大模型輔助下的完整走查3.1 閉環(huán)第一步從行為描述生成測試骨架真正的 AI-TDD 循環(huán)不是“讓 AI 直接寫全部代碼”而是讓 AI 進(jìn)入 TDD 的每一步但不破壞紅綠交替的節(jié)奏。第一步把 Gherkin 場景作為輸入讓大模型生成可執(zhí)行的測試骨架。我給模型的 prompt 通常是這樣的模式你是一名測試工程師。請根據(jù)以下 Gherkin 場景生成 pytest-bdd 風(fēng)格的測試代碼。 要求 1. 不實現(xiàn)任何業(yè)務(wù)邏輯。 2. 每個 Then 步驟必須包含一個真實斷言。 3. 使用 fixtures 或 mocks 隔離外部依賴。 4. 輸出完整 Python 文件給出文件名。 Gherkin: ...生成后直接放到測試目錄運行一次。在這一步測試的全部目的就是失敗。失敗的姿勢不重要可能是缺少實現(xiàn)類、缺少路由、缺少數(shù)據(jù)庫表重要的是系統(tǒng)已經(jīng)出現(xiàn)了一組“表達(dá)需求契約”的可執(zhí)行用例。這一階段我會強(qiáng)令自己和團(tuán)隊不允許跳過紅燈直接進(jìn)入實現(xiàn)。因為紅燈是團(tuán)隊對需求的共同確認(rèn)清空這個狀態(tài)很容易讓需求又變回口頭共識。3.2 閉環(huán)第二步讓大模型補(bǔ)全失敗用例與邊界條件第一輪生成的測試通常只覆蓋主流程。我不會滿足于此而是把已有測試代碼和 Feature 文件一起作為上下文再追加一輪 prompt“請針對上述場景補(bǔ)充等價類、邊界值、異常路徑測試新增測試必須是當(dāng)前需求支持的不要臆造業(yè)務(wù)規(guī)則?!边@一步會得到大量額外測試金額正好等于閾值、會員與普通用戶差異、重復(fù)提交、庫存不足等等。需要一個人工審查動作逐條判斷這些補(bǔ)充用例是否真的符合業(yè)務(wù)不確定的就標(biāo)記為“待業(yè)務(wù)確認(rèn)”。這正好解決了 AI 生成內(nèi)容最大的隱患——它經(jīng)常把“可能存在的規(guī)則”當(dāng)成必然規(guī)則。邊界用例一旦進(jìn)入測試套件就會變成強(qiáng)約束所以人工閘門不能省。補(bǔ)完后再次運行測試確認(rèn)所有新用例仍處于失敗狀態(tài)。如果某些測試意外通過往往是測試本身寫得太弱或者被實現(xiàn)代碼“猜中”了需要回爐加強(qiáng)斷言。3.3 閉環(huán)第三步紅期失敗驅(qū)動下的實現(xiàn)代碼生成當(dāng)一組失敗測試穩(wěn)定出現(xiàn)在面前時再把它們作為輸入讓大模型生成最小實現(xiàn)。此時的上下文包含失敗測試代碼、測試輸出、相關(guān)接口定義以及一句強(qiáng)約束“你可以新增實現(xiàn)代碼但絕不能修改測試代碼和 Feature 文件?!贝竽P驮凇翱粗鴾y試寫實現(xiàn)”時的表現(xiàn)通常好于“直接看需求寫代碼”。原因是斷言本身提供了清晰的目標(biāo)函數(shù)。實現(xiàn)只要做到讓測試通過即可這讓模型不必在需求空間里做過多推理。實際執(zhí)行中第一次生成的實現(xiàn)可能只讓一部分測試通過剩下的失敗信息會再次反饋給模型。這種循環(huán)很像結(jié)對編程里的“輪到你了”。每輪反饋都用真實測試輸出作為依據(jù)避免了模型空轉(zhuǎn)。要特別強(qiáng)調(diào)的是模型被“追得太緊”時會嘗試修改測試來制造綠你必須把“不允許修改測試”寫進(jìn) prompt 之外還要用 CI 或文件權(quán)限卡住測試目錄的寫權(quán)限。3.4 閉環(huán)第四步重構(gòu)期由 AI 承擔(dān)機(jī)械改造人審語義測試全綠后進(jìn)入重構(gòu)階段。傳統(tǒng)重構(gòu)需要開發(fā)者手工消除重復(fù)、拆分函數(shù)、命名調(diào)整這些工作大模型完全可以承擔(dān)而且速度驚人。我會在 IDE 里選一段剛通過的代碼讓模型“在不改變現(xiàn)有公共行為的前提下提取重復(fù)邏輯并重命名變量”然后把結(jié)果和測試一起提交。但這里有一個必須遵守的原則AI 重構(gòu)時不要喂給它過大的上下文只給它局部片段和就近的測試運行結(jié)果。它進(jìn)行重構(gòu)時你需要在旁邊看 diff。實測中小模型傾向于做非常保守的調(diào)整強(qiáng)模型則喜歡“順手優(yōu)化”出一些抽象層。抽象本身不一定是壞事但如果它與當(dāng)前測試沒有直接關(guān)聯(lián)就容易變成過度設(shè)計。我給團(tuán)隊的策略是AI 的重構(gòu)結(jié)果必須重新跑一次全部測試并且代碼評審人至少有一個人真正理解這塊業(yè)務(wù)。到此一個完整的 Behavior-Driven AI-TDD 閉環(huán)就形成了。它不是一個松散的自由發(fā)揮式 AI 編碼而是一套有紀(jì)律、有紅綠信號、有反饋循環(huán)的工程流程。4. 工具鏈與模型選型本地大模型與商用 API 怎么塞進(jìn)工作流4.1 工作流對模型能力的三個硬性要求不是隨便什么大模型都能塞進(jìn)這套閉環(huán)。我在選型時主要看三個能力維度。第一是長上下文。一個真實的 Feature 文件加對應(yīng)測試文件經(jīng)常有兩三百行模型必須能在輸入里同時容納這些內(nèi)容才不會漏掉場景。第二是指令遵循能力。它必須能聽懂“只生成測試代碼”“不要修改測試”“不要輸出解釋文本”這類要求。有的模型生成效果好但總在代碼塊前后夾帶說明文字用起來很別扭。第三是穩(wěn)定性。同樣的輸入重復(fù)調(diào)用如果輸出測試每次都不一樣團(tuán)隊會被調(diào) prompt 耗死。這三個要求直接把模型分成了兩個陣營輕量本地模型和重型商用 API。測試代碼生成這種任務(wù)本地 7B 到 14B 量級的模型基本能勝任涉及復(fù)雜業(yè)務(wù)邏輯重構(gòu)或需求語義推斷時調(diào)用能力更強(qiáng)的 API 成功率會高很多。我的做法是“雙軌并跑”團(tuán)隊默認(rèn)使用本地模型處理日常封閉循環(huán)遇到生成質(zhì)量連續(xù)不達(dá)標(biāo)的情況再顯式切換到遠(yuǎn)程強(qiáng)模型。4.2 本地部署模型在測試代碼生成場景的實測印象我在一臺 32GB 顯存的機(jī)器上跑過量化后的 7B 和 14B 指令模型用 Ollama 暴露 OpenAI 兼容接口然后接進(jìn)一個小 CLI 工具。生成簡單的 pytest 測試完全沒有問題Given/When/Then 步驟基本能對上。但面對一個包含五個以上場景的 Gherkin 文件時小模型會漏場景尤其是在生成第二、第三個場景時容易開始“合并同類項”。對策是把一個 Feature 按場景拆開每次只喂給模型一個場景生成后再合并。這犧牲了一點速度但質(zhì)量顯著提升。對于本地模型的量化精度我的建議是優(yōu)先用 4bit 或 5bit 量化不要一味壓到 2bit。測試代碼的長尾錯誤信息和邊界斷言需要足夠的指令跟隨能力量化太狠會直接表現(xiàn)為“生成即崩”。如果你所在項目有數(shù)據(jù)合規(guī)要求不能把業(yè)務(wù)代碼發(fā)送到外部 API本地部署幾乎是唯一選擇。把 Feature 文件和測試代碼留在內(nèi)網(wǎng)讓模型走本地推理整套流程依然能閉環(huán)。代價是你需要抽時間維護(hù)模型版本和推理服務(wù)這種運維成本要算進(jìn)整體工作流重構(gòu)的預(yù)算里。4.3 配合現(xiàn)有 CI 的自動化卡點設(shè)計工作流重構(gòu)最終要落到 CI 上否則“紅綠循環(huán)”很容易只存在于政治正確的日報里。我目前用的做法是在代碼倉庫里約定三個目錄features/存放 Gherkin 文件tests/存放測試代碼src/存放實現(xiàn)代碼。當(dāng) PR 里出現(xiàn)features/*.feature變更時CI 會自動執(zhí)行一次“行為一致性檢查”# 偽代碼博文示例實際按項目調(diào)整 parse-features features/ /tmp/bdd_steps.json run-tests tests/ /tmp/test_result.json validate-mapping /tmp/bdd_steps.json /tmp/test_result.json這個卡點只驗證兩件事Feature 文件里的每個場景是否能找到對應(yīng)測試方法以及這些測試是否全部執(zhí)行過。它不會替代代碼評審但能把“Feature 文件和測試脫節(jié)”這類問題在合并前攔下來。CI 還可以做一道反向卡點如果某次提交只改了實現(xiàn)代碼卻沒有任何測試文件一起變化就自動標(biāo)記為“需要人工確認(rèn)測試是否充分”。這在 AI 輔助生成大量代碼后尤其重要能避免模型悄悄繞過紅燈階段。5. 落地過程中的坑與對策AI 生成的測試也要被質(zhì)疑5.1 大模型會自信地寫出“假綠”測試AI-TDD 最大的坑不是 AI 不會寫代碼而是它會用非常漂亮的姿勢寫出沒有價值的測試。最常見的情況包括斷言為空、只用assert True、把實現(xiàn)邏輯復(fù)制進(jìn)測試、或者只 mock 了所有依賴然后斷言 mock 被調(diào)用了自己。def test_discount(): assert True # 假綠剛開始我把這類測試混進(jìn)套件時CI 全綠覆蓋數(shù)據(jù)很漂亮但一改折扣規(guī)則測試照樣通過。原因就是斷言沒有錨定可觀察行為?,F(xiàn)在我會在生成 prompt 里強(qiáng)寫一條規(guī)則“每個測試必須包含至少一個不依賴被測函數(shù)內(nèi)部實現(xiàn)的數(shù)據(jù)斷言禁止使用 assert True。”代碼評審時第一眼永遠(yuǎn)看斷言而不是看測試命名。更有效的查錯手段是變異測試。對實現(xiàn)代碼里的常量做小改動比如把八折改成七折如果測試套件沒有變紅說明測試根本沒捕捉到行為變化。這個檢查成本略高但對驗證 AI 生成測試的質(zhì)量特別好用。我在合入大段 AI 生成的測試前都會至少跑一次針對核心業(yè)務(wù)常量的變異檢查。5.2 保持紅綠循環(huán)的頻率讓 AI 反饋在 10 分鐘以內(nèi)如果一次生成測試要等五分鐘生成實現(xiàn)又要等五分鐘開發(fā)節(jié)奏會被 AI 拖垮。人沒辦法在每分鐘一次的反饋循環(huán)里長期工作大腦會主動繞開這個高摩擦環(huán)節(jié)。解決方向是縮小工作單元。一個完整的 Feature 往往拆成若干個 Scenario每個 Scenario 就是一個循環(huán)單元。只針對當(dāng)前場景生成測試再生成實現(xiàn)跑完進(jìn)入下一個場景。這樣單次生成量控制在 50 行以內(nèi)絕大多數(shù)本地模型能在 20 秒到 1 分鐘內(nèi)完成。整個紅綠循環(huán)的自然節(jié)奏被保持下來。另外一個改變是讓 AI 直接生成差量文件而不是每次都重寫整個測試文件。我在 CLI 工具里會把當(dāng)前文件內(nèi)容和新增場景追加進(jìn) prompt要求模型只輸出新增部分再由腳本合并。這樣生成的輸出短、模型不容易幻覺且已有代碼不會被無意義改動。5.3 代碼審查的側(cè)重點要改變傳統(tǒng)代碼評審主要看實現(xiàn)邏輯質(zhì)量。到了 AI-TDD 工作流里實現(xiàn)代碼很可能是模型寫的評審人的精力要更多花在“測試與需求之間的映射”上。我一般會帶著三類問題去審每個 Gherkin 步驟是不是都變成了一個真實斷言測試?yán)镉袥]有偷偷寫入實現(xiàn)細(xì)節(jié)導(dǎo)致測試和需求耦合過深有沒有出現(xiàn)大量“為了覆蓋率而測”的無效用例這些問題看起來簡單但 AI 生成的代碼很容易混入“裝飾性測試”——它能跑、有斷言、但業(yè)務(wù)價值趨近于零。人工評審必須有意識地做減法敢于刪掉模型生成的多余測試。5.4 團(tuán)隊協(xié)作契約AI 輔助生成物必須可追溯最后是協(xié)作層面的問題。AI 生成物越來越多如果不知道哪段測試來自哪個模型、哪次 prompt出問題時就很難復(fù)盤。我給團(tuán)隊定了三條規(guī)則Feature 文件是需求的唯一事實來源任何人不得繞過它直接讓 AI 寫測試。所有由 AI 生成的測試文件頭部保留生成配置注釋記錄模型名稱、溫度參數(shù)、生成命令。任何模型輸出只要被人工修改就不準(zhǔn)承諾“這是 AI 原生成品”。這三條規(guī)則不需要工具支撐靠代碼審查就能執(zhí)行。但它們決定了 AI-TDD 是一個可治理的工程流程還是又一場 prompt 煙花秀。6. 一個支付模塊的重寫案例幾十次紅綠循環(huán)之后的工作流重構(gòu)體會6.1 我們是怎么從一個混亂模塊開始的這個支付模塊原本有 21% 的行覆蓋率大部分測試都是“調(diào)用函數(shù)斷言不為空”級別的。業(yè)務(wù)邏輯散落在服務(wù)層和一個 900 行的工具類里沒人敢動。重寫的起點不是先寫代碼而是先和業(yè)務(wù)方坐下來把歷史遺留的各種“應(yīng)該”“可能”梳理成一份 Gherkin 文件。這個過程花了三天產(chǎn)出了 20 個場景覆蓋主流程、異常、邊界。當(dāng)這些場景變成一個一個紅燈時團(tuán)隊的討論方式發(fā)生了變化。以前驗收測試的問題常常是“你覺得這個地方應(yīng)該怎么算”現(xiàn)在變成了“這個 Scenario 寫的是金額滿 200 就打折但如果訂單里有運費當(dāng)前場景沒有描述運費是否計入閾值”。業(yè)務(wù)方會直接回答說“運費不計入”我們就順手把這個約束補(bǔ)進(jìn) Given 里。這類歧義在傳統(tǒng)開發(fā)里通常會被實現(xiàn)者靜默消化掉現(xiàn)在卻能在測試變綠前被顯式解決。6.2 機(jī)械勞動被替代之后人的工作重心變了重構(gòu)過程中模型承擔(dān)了大約八成測試骨架和一多半機(jī)械重構(gòu)。真正省下來的時間不是“不用寫測試了”而是“不用在測試?yán)锊聵I(yè)務(wù)規(guī)則”。那些省下來的精力被放到了與業(yè)務(wù)對齊、審查斷言、刪掉無效測試上。幾十次紅綠循環(huán)后這個模塊的測試數(shù)量從 17 個變成 52 個覆蓋率到了 84%。我個人的體會是這套工作流最大的價值不在效率提升多少倍而是讓 AI 的輸出從“看起來像代碼”變成了“可被行為契約校準(zhǔn)的工程產(chǎn)物”。AI 出錯不可怕可怕的是出錯時沒有任何信號能發(fā)現(xiàn)。在 Behavior-Driven AI-TDD 里紅綠狀態(tài)和 Gherkin 場景就是那組信號。如果你也正打算把大模型接進(jìn)開發(fā)流程我建議先別急著搭平臺而是拿一個需求邊界清晰的模塊把 Feature 文件、測試生成、實現(xiàn)生成、手動測試這四步先手工跑通一次。一旦你體會到“紅燈代表需求契約綠燈代表行為滿足”的感覺就不會再回到直接讓 AI 寫完整代碼的老路了。