大廠實(shí)踐: LLM 加速大規(guī)模測試遷移
Airbnb最近完成了第一次由 LLM 驅(qū)動的大規(guī)模代碼遷移,將 3500 個(gè)測試文件從 Enzyme 更新為 React測試庫(RTL,React Testing Library)。最初我們估計(jì)這需要 1 年半的時(shí)間來手工完成,但通過使用前沿模型和強(qiáng)大的自動化組合,我們在 6 周內(nèi)完成了整個(gè)遷移。
本文將重點(diǎn)介紹從 Enzyme 遷移到 RTL 所面臨的獨(dú)特挑戰(zhàn),如何通過 LLM 解決這些挑戰(zhàn),以及如何構(gòu)建遷移工具來執(zhí)行 LLM 驅(qū)動的大規(guī)模遷移。
一、背景
2020 年,Airbnb 采用 React 測試庫(RTL)進(jìn)行所有新的 React 組件測試開發(fā),標(biāo)志著我們邁出了遠(yuǎn)離 Enzyme 的第一步。盡管自 2015 年以來,Enzyme 一直為我們提供良好的服務(wù),但它是為 React 的早期版本設(shè)計(jì)的,并且該框架對組件內(nèi)部的深度訪問不再符合現(xiàn)代 React 測試實(shí)踐。
但由于框架之間存在根本性差異,我們無法輕易替換(閱讀 Introducing the React Testing Library[2] 獲取更多關(guān)于差異的信息)。而且分析發(fā)現(xiàn)如果僅僅刪除 Enzyme 文件,會在代碼覆蓋率中造成顯著缺口。為了完成遷移,需要一種自動化方法來將測試文件從 Enzyme 重構(gòu)為 RTL,同時(shí)保留原始測試意圖以及代碼覆蓋率。
二、如何做到
2023 年中,Airbnb 的一個(gè)黑客馬拉松團(tuán)隊(duì)展示了大語言模型可以在短短幾天內(nèi)成功將數(shù)百個(gè) Enzyme 文件轉(zhuǎn)換為 RTL。
在這個(gè)很有希望的結(jié)果基礎(chǔ)上,我們在 2024 年為 LLM 驅(qū)動的遷移開發(fā)了一個(gè)可擴(kuò)展流水線。我們將遷移分解為離散的、可以并行化每個(gè)文件步驟和配置的重試循環(huán),并通過額外的上下文顯著擴(kuò)展了提示詞。最后,對復(fù)雜文件的長尾執(zhí)行了寬度優(yōu)先的提示詞調(diào)優(yōu)。
1. 文件驗(yàn)證和重構(gòu)步驟
首先將遷移分解為一系列自動驗(yàn)證和重構(gòu)步驟。可以把它想象成一個(gè)生產(chǎn)流水線:每個(gè)文件都經(jīng)過驗(yàn)證階段,當(dāng)檢查失敗時(shí),就引入 LLM 來修復(fù)。
我們將此流程建模為狀態(tài)機(jī),只有在前一個(gè)狀態(tài)通過驗(yàn)證后才將文件移動到下一個(gè)狀態(tài):
這種分步驟的方法為自動化流水線提供了堅(jiān)實(shí)基礎(chǔ),使我們能夠跟蹤進(jìn)度,優(yōu)化特定步驟的故障率,并在需要時(shí)重新運(yùn)行文件或步驟。基于步驟的方法還使同時(shí)在數(shù)百個(gè)文件上運(yùn)行遷移變得簡單,這對于快速遷移簡單文件和在遷移過程中逐漸消除長尾文件至關(guān)重要。
2. 重試循環(huán)和動態(tài)提示
在遷移早期,我們嘗試了不同的提示工程策略來提高每個(gè)文件遷移的成功率。然而,在分步驟方法的基礎(chǔ)上,我們發(fā)現(xiàn)改善結(jié)果的最有效途徑是簡單的蠻力:多次重試步驟,直到通過或達(dá)到極限。我們更新了步驟,為每次重試使用動態(tài)提示,將驗(yàn)證錯(cuò)誤和文件的最新版本提供給 LLM,并構(gòu)建了循環(huán)執(zhí)行器,可以配置每個(gè)步驟的嘗試次數(shù)。
通過簡單的重試循環(huán),我們發(fā)現(xiàn)可以成功遷移大量簡單到中等復(fù)雜度的測試文件,其中有些需要重試幾次才能成功完成,大多數(shù)可以在 10 次嘗試后成功完成。
3. 擴(kuò)展上下文
對于具有一定復(fù)雜度的測試文件,只需增加重試次數(shù)就可以了。然而,要處理具有復(fù)雜的測試狀態(tài)設(shè)置或過多間接文件,我們發(fā)現(xiàn)最好的方法是將盡可能多的相關(guān)上下文放入提示詞中。
在遷移結(jié)束時(shí),我們的提示已經(jīng)擴(kuò)展到 40,000 到 100,000 個(gè) token,涉及多達(dá) 50 個(gè)相關(guān)文件,大量手工編寫的示例,以及來自同一項(xiàng)目中現(xiàn)有的、編寫良好的、通過測試的文件示例。
每個(gè)提示詞包括:
- 被測組件的源代碼
- 正在遷移的測試文件
- 驗(yàn)證失敗的步驟
- 來自同一目錄的相關(guān)測試(維護(hù)團(tuán)隊(duì)特定模式)
- 一般性的遷移指南和通用解決方案
下面是實(shí)際應(yīng)用中的樣子(為了可讀性做了部分修改):
// Code example shows a trimmed down version of a prompt
// including the raw source code from related files, imports,
// examples, the component source itself, and the test file to migrate.
const prompt = [
'Convert this Enzyme test to React Testing Library:',
`SIBLING TESTS:\n${siblingTestFilesSourceCode}`,
`RTL EXAMPLES:\n${reactTestingLibraryExamples}`,
`IMPORTS:\n${nearestImportSourceCode}`,
`COMPONENT SOURCE:\n${componentFileSourceCode}`,
`TEST TO MIGRATE:\n${testFileSourceCode}`,
].join('\n\n');
這種豐富的上下文方法被證明對更復(fù)雜的文件非常有效,LLM 可以更好的理解團(tuán)隊(duì)的特定模式、通用測試方法和代碼庫的整體體系架構(gòu)。
應(yīng)該注意到,盡管我們在這個(gè)步驟中做了一些提示工程,但主要成功驅(qū)動因素是選擇正確的相關(guān)文件(查找附近的文件,來自同一個(gè)項(xiàng)目的好的示例文件,過濾與組件相關(guān)的文件的依賴項(xiàng),等等),而不是依賴更完美的提示工程。
通過重試、構(gòu)建豐富的上下文以及測試遷移移腳本之后,當(dāng)我們進(jìn)行第一次批量運(yùn)行時(shí),在短短 4 個(gè)小時(shí)內(nèi)就成功遷移了 75% 的目標(biāo)文件。
4. 從 75% 到 97%:系統(tǒng)化改進(jìn)
75% 的成功率確實(shí)令人興奮,但仍然有近 900 個(gè)文件沒有達(dá)到驗(yàn)證標(biāo)準(zhǔn)。為了解決這個(gè)長尾問題,我們需要一種系統(tǒng)化方法來了解剩余文件卡在哪里,并改進(jìn)遷移腳本來解決這些問題。我們希望首先擴(kuò)展廣度,積極減少剩余文件,而不要被最困難的遷移案例所困。
為此,我們在遷移工具中構(gòu)建了兩個(gè)特性。
- 首先,我們構(gòu)建了一個(gè)簡單的系統(tǒng),通過在文件中添加自動生成的注釋來記錄每個(gè)遷移步驟的狀態(tài),從而使我們能夠看到腳本所面臨的常見問題。下面是代碼注釋的樣子:
// MIGRATION STATUS: {"enyzme":"done","jest":{"passed":8,"failed":2,"total":10,"skipped":0,"successRate":80},"eslint":"pending","tsc":"pending",}
- 其次,我們添加了輕松重新運(yùn)行單個(gè)文件或路徑模式的能力,根據(jù)它們所攜帶的特定步驟進(jìn)行過濾:
$ llm-bulk-migration --step=fix-jest --match=project-abc/**
基于這兩個(gè)功能,我們可以快速運(yùn)行反饋循環(huán)來改進(jìn)提示和工具:
- 運(yùn)行所有剩余的失敗文件,以找到 LLM 卡住的常見問題
- 選擇文件樣本(5 到 10 個(gè))來說明某個(gè)常見問題
- 更新提示詞和腳本來解決這個(gè)問題
- 重新運(yùn)行失敗文件樣本以驗(yàn)證修復(fù)
- 再次對所有剩余的文件執(zhí)行上述操作
在運(yùn)行這個(gè)“采樣、調(diào)整、掃描”循環(huán) 4 天后,我們已經(jīng)將完成的文件從所有文件的 75% 推到了 97%,只剩下了不到 100 個(gè)文件。到目前為止,我們已經(jīng)對許多長尾文件進(jìn)行了 50 到 100 次重試,似乎已經(jīng)達(dá)到了通過自動化修復(fù)的極限。我們沒有投入更多調(diào)優(yōu),而是選擇手動修復(fù)剩余文件,從基線(失敗)開始重構(gòu),從而減少修復(fù)這些文件的工作量。
三、結(jié)果及影響
有了驗(yàn)證和重構(gòu)流水線、重試循環(huán)和擴(kuò)展上下文,我們能夠在4小時(shí)內(nèi)自動遷移 75% 的目標(biāo)文件。
經(jīng)過四天的“采樣、調(diào)優(yōu)和掃描”策略實(shí)現(xiàn)的提示詞和腳本優(yōu)化,我們完成了 3500 個(gè)原始 Enzyme 文件的97%。
對于剩余的 3% 沒有通過自動化完成的文件,腳本為手動干預(yù)提供了一個(gè)很好的基線,幫助我們在一周之內(nèi)完成了剩余文件的遷移。
最重要的是,我們能夠在保持原始測試意圖和代碼覆蓋率的同時(shí)替換 Enzyme。即使在遷移的長尾上有很高的重試次數(shù),總成本(包括 LLM API 的使用和 6 周的工程時(shí)間)被證明比最初手動遷移的估算要高效得多。
四、下一步
這種遷移突出了 LLM 對大規(guī)模代碼轉(zhuǎn)換的能力。我們計(jì)劃擴(kuò)展這種方法,開發(fā)更復(fù)雜的遷移工具,并探索 LLM 驅(qū)動的自動化的新應(yīng)用,以提高開發(fā)人員的生產(chǎn)力。
參考資料
- [1] Accelerating Large-Scale Test Migration with LLMs: https://medium.com/airbnb-engineering/accelerating-large-scale-test-migration-with-llms-9565c208023b
- [2] Introducing the React Testing Library: https://kentcdodds.com/blog/introducing-the-react-testing-library