一、為什麼需要重新思考 Regression Test
每個有點規模的工程團隊,最終都會遇到同一個牆:測試套件膨脹到讓 CI 變慢、開發回饋變遲、commit 之間排隊塞車。
走過這條路的團隊,通常嘗試過下面四種解法,每一種都有明顯天花板:
解法一:多開 CI Runner 平行化
短期有效,但硬體成本線性上升,而且不解決測試套件本身臃腫的問題。你只是用錢換時間,撐不久。
解法二:分層測試
PR 只跑單元測試,合併後才跑整合測試,夜間才跑 E2E。問題是 bug 被往後推延,真正昂貴的整合 bug 反而最晚被發現。
解法三:靜態分析做測試選擇(Static RTS)
根據 import graph 推算「改 X 檔可能影響哪些測試」。問題是依賴圖一旦變大就不準,跨語言完全失效,而且抓不到因設定檔、資料變動造成的隱性依賴。
解法四:人工分類測試標籤
@smoke、@critical 等標籤,靠人工經驗決定 PR 該跑哪些。問題是這需要持續維護,而且人的直覺往往落後實際情況。
四種方法的共同盲點:
它們都試圖從程式碼結構推論「應該」跑哪些測試,而沒有看「過去實際上是什麼測試在抓到 bug」。
Launchable 解決的就是這件事。
二、Launchable 是什麼
Launchable 是一套以機器學習為核心的測試最佳化平台,核心功能叫做 Predictive Test Selection(預測性測試選擇,以下簡稱 PTS)。它的概念極為簡潔:
對每一次程式碼變更,從歷史資料學習「哪些測試最有可能失敗」,只跑這些測試,其餘留給 defensive run。
關鍵差異在於:它不看程式碼本身,只看 commit metadata 加上測試結果歷史。也就是說,它不需要解析你的語言、不需要建依賴圖、不需要 instrumentation。它只需要知道「這次 commit 改了哪些檔」以及「過去類似的變更弄壞了哪些測試」。
這個方法從哪裡來
這個方法源自 Meta(Facebook)和 Google 的內部研究。Meta 在 2018 年公開《Predictive Test Selection》論文,描述他們在 Facebook 內部 monorepo 部署這套系統一年多後,只跑大約三分之一的相關測試,就抓到 99.9% 的 regression bug,讓測試基礎設施效率翻倍。
Launchable 是由其中一位作者 Kohsuke Kawaguchi(也是 Jenkins 創始人)離開 Meta 後創立的公司,目的是把這套方法包成 SaaS,讓任何規模的團隊都能採用。
一張圖看懂差別
最直接的方式是看一個典型 PR 在兩種做法下的執行流程:

圖一:傳統做法 vs Launchable PTS 的執行流程對比
三、PTS 為什麼會成立:三個關鍵洞見
要理解為什麼 PTS 不是炒作而是有效方法,要先理解三個核心洞見。
洞見一:測試失敗有強烈的時空局部性
如果你蒐集任何中型專案半年的測試紀錄,會發現一個明顯模式:少數測試承擔了絕大多數的失敗事件。這不是因為那些測試品質差,而是因為它們處在「高風險路徑」上 — 涵蓋的程式碼經常變動、邏輯複雜、被多個模組依賴。
同樣的,少數檔案承擔了絕大多數的變更。一個典型的 Pareto 分布:20% 的檔案吸收 80% 的 commit。

圖二:測試失敗與檔案變更都呈現 80/20 分布
這兩個 80/20 一旦交叉,就形成可預測的「風險熱點」。PTS 做的事就是把這個熱點量化。
洞見二:歷史優於結構
舉個具體例子:你的專案有個 pricing.py,還有一個整合用的 checkout.py,checkout 會 import pricing。靜態分析告訴你「改 pricing 會影響 checkout」 — 沒錯。但靜態分析不會告訴你哪一個 checkout 測試最容易壞。
歷史資料可以。如果過去六個月,每次改 pricing.py 都有 40% 機率弄壞 test_checkout_with_discount,但只有 5% 機率弄壞 test_checkout_jp_region — 這個訊號是程式碼結構分析永遠無法給你的。它隱含了真實的開發模式、商業邏輯重點、團隊容易犯的錯誤類型。
結構告訴你「可能」,歷史告訴你「實際」。這也是為什麼 Launchable 完全不需要看你的原始碼。
洞見三:不需要 100% 信心,只要可量化的信心
很多團隊抗拒 PTS 的第一反應是:「萬一漏了某個測試怎麼辦?」這個問題的答案分兩層。
第一層,Launchable 不要求你只跑子集就 merge。它的設計是分層測試:每個 push 跑高信心子集(快速回饋),merge 前或夜間跑完整套件(defensive run)。這樣即使子集漏掉了某個 regression,defensive run 一定會抓到 — 而且模型會從這個漏掉的事件中學習,下次更準。
第二層,Launchable 把信心量化成一個可調的旋鈕。你可以說「我要 90% 信心」,系統就會告訴你「那需要跑這 N 個測試」。這個 trade-off 從黑盒變成可調參數。

圖三:Confidence Curve — 信心是個可調的旋鈕
曲線的形狀很重要:前 15% 的測試就能拿到 90% 信心,但要從 90% 提升到 99% 卻需要再跑 35% 的測試。這就是為什麼分層策略有效 — 日常開發用 90% 信心拿到大部分價值,merge 前用 99% 信心做最後把關。
四、Launchable 的內部運作機制
把上面的洞見落實成系統,Launchable 內部主要做四件事。
第一件事:資料採集
每次 build 跑完,Launchable 收集兩類資料:
- Build metadata:透過 launchable record build 命令傳送。包含 git commit hash、變更的檔案清單、每個檔案變更的行數、commit 訊息、作者等。重點是不包含原始碼本身,Launchable 不會看到你的商業邏輯。
- 測試結果:透過 launchable record tests 命令傳送 JUnit XML 或類似格式的報告。包含每個測試的名稱、執行時間、pass/fail 狀態、錯誤訊息摘要等。
這兩類資料形成 PTS 模型的訓練語料 — 「這次 build 改了什麼,結果哪些測試壞了」。每個客戶的模型獨立訓練,夜間更新,確保不會混雜其他公司的模式。
第二件事:特徵工程
原始的 (changed_files, failed_tests) 並不直接餵給模型。Launchable 會抽取多種特徵,大致分三類:
變更端特徵
這次改了幾個檔、改的檔案分布在哪些目錄、檔案變更的行數、commit 大小、作者過去的模式、距離上次 main 合併多久。
測試端特徵
這個測試上次跑是什麼時候、最近 N 次的 pass rate、平均執行時間、是否被標記為 flaky、過去常常和哪些其他測試一起失敗。
交叉特徵(最關鍵)
這個檔案歷史上被修改時、這個測試失敗的條件機率;這對 (檔, 測試) 在最近 K 個時間窗口內的共現次數;檔案路徑與測試路徑的字串相似度等。
這些特徵餵給模型後,輸出每個測試對「這次 PR」的風險分數。
第三件事:模型本身
Launchable 內部用的是 Gradient Boosted Decision Trees(GBDT),也就是 Meta 原始論文採用的同一種模型。GBDT 在這個場景特別合適,有四個原因:
- 不需要正規化特徵值
- 訓練速度快、在資料量不大時也穩定
- 能處理正負樣本嚴重不平衡(失敗事件遠少於通過事件)
- 原生支援類別型特徵(像是檔案路徑、作者名稱)
模型輸出是給每個 (PR, 測試) pair 一個 0-1 之間的風險分數。系統不會做硬性的「跑/不跑」決策,而是把所有測試按分數排序,讓使用者用 confidence 參數決定要切在哪。
第四件事:Flakiness 處理
這是 Launchable(和 Meta 的原始論文)花最多力氣處理的問題。Flaky 測試 — 也就是有時通過、有時失敗、但程式碼沒變的測試 — 會嚴重污染訓練資料。如果不處理,模型會誤以為某些檔案「很容易弄壞」某個其實只是隨機 fail 的測試,從而把根本不相關的測試一直推到子集裡。
Launchable 用幾個方法偵測 flakiness:統計同一個測試在沒有相關程式碼變更時的失敗率、看測試的失敗訊息是否一致、追蹤重跑後的結果。被判定 flaky 的測試會降低權重或從訓練資料中排除。
這也是為什麼 Launchable 的真實效果通常需要兩到四週的訓練期 — 系統需要時間累積資料、判別哪些測試是真正的訊號、哪些是雜訊。
五、三層測試策略:速度與信心的分層配置
理解了模型,下一步是把它導入工作流。Launchable 的最佳實踐是「三層測試」 — 不同階段用不同 confidence,讓 bug 在最早階段就被抓到,但不犧牲總體信心。

圖四:三層測試策略,讓 bug 在最早階段被發現
為什麼這樣設計
這個架構的精妙在於:每一層都有明確的目標和代價。第一層追求速度,第二層追求把關,第三層追求完整覆蓋與模型訓練。
Defensive Run 不能省。
曾有團隊為了極致省成本,完全廢掉 defensive run 只跑子集 — 這是危險的。失去 defensive run 就失去了模型學習的閉環,長期模型會退化。建議至少每晚跑一次完整套件,結果回流訓練。
六、完整範例:一個電商團隊如何用 Launchable 挑測試
抽象講夠了,來看具體情境。
背景設定
假設一家中型電商公司的後端服務有以下測試套件:
- 1,200 個單元測試,跑完約 15 分鐘
- 350 個整合測試,跑完約 30 分鐘
- 80 個 E2E 測試,跑完約 60 分鐘
合計 1,630 個測試、105 分鐘。團隊有 40 個工程師,每天合計推約 80 個 PR。每個 PR 跑完整套件不可能,目前的折衷是 PR 只跑單元測試(15 分鐘),整合與 E2E 留到夜間。
問題:整合測試發現的 bug 平均要在 commit 後 14 小時才被注意到。工程師早就 context-switch 到下個任務,修起來慢且痛苦。
導入第一週:接上 Launchable
工程師 Alice 在 CI 腳本裡加了三段命令。原本的 .github/workflows/test.yml 大致長這樣(簡化):
– name: Run tests
run: pytest tests/ –junit-xml=results.xml
加上 Launchable 後變成:
– name: Record build to Launchable
run: launchable record build –name ${{ github.sha }} –source src=.
– name: Run tests
run: pytest tests/ –junit-xml=results.xml
– name: Record results to Launchable
if: always()
run: launchable record tests –build ${{ github.sha }} pytest results.xml
注意第一週還沒做測試選擇。團隊只是先把資料餵進去,讓 Launchable 累積訓練語料。整合與 E2E 測試也保持夜間跑全套,結果一樣回報。這個階段 CI 時間不變,但 Launchable 在背景觀察。
第三週:啟用 PR 階段的子集執行
兩週後,Launchable 蒐集了約 800 次 build 的資料,模型穩定了。Alice 在 PR 工作流加上 subset 命令:
– name: Get test subset
run: |
pytest –collect-only -q > test_list.txt
cat test_list.txt | launchable subset \
–build ${{ github.sha }} \
–confidence 90% \
pytest > launchable-subset.txt
– name: Run subset
run: pytest $(cat launchable-subset.txt) –junit-xml=results.xml
現在每個 PR 只跑 Launchable 推薦的 90% 信心子集。
一個 PR 的真實情境
工程師 Bob 提了一個 PR,改了 src/promotion/coupon_validator.py,一個處理優惠券驗證的模組,8 行變更。
CI 觸發 Launchable 的子集命令。Launchable 在背景做了下列事情:
它從 git diff 看到只有 coupon_validator.py 一個檔案被改。它查詢這個檔案在過去三個月的歷史:被修改過 47 次,在這 47 次裡,失敗事件分布如下:
coupon_validator.py 的歷史失敗分布:
• test_coupon_apply_percentage:失敗 18 次 (38%)
• test_coupon_apply_fixed_amount:失敗 14 次 (30%)
• test_coupon_expiry_check:失敗 11 次 (23%)
• test_checkout_with_coupon(整合測試,跨目錄):失敗 9 次 (19%)
• test_promotion_code_unique(整合測試):失敗 6 次 (13%)
• test_user_total_savings(E2E 測試):失敗 4 次 (9%)
• 另外 23 個其他測試零星失敗過 1-2 次
模型對這次 PR 計算每個測試的風險分數,排序後把累積風險達到 90% 信心所需的測試取出來。最終輸出 14 個測試:6 個 coupon 直接相關的單元測試 + 4 個 promotion 與 checkout 整合測試 + 2 個 E2E + 2 個過去偶發失敗的邊角測試。
Bob 的 PR 跑這 14 個測試只花 4 分鐘。
重點是其中包含 2 個 E2E 測試 — 過去這在 PR 階段絕對不會跑,但模型認為它們對這次特定變更很重要。
果然,其中一個 E2E 測試 test_user_total_savings 失敗了。Bob 的優惠券邏輯改動在某種多優惠券疊加情境下計算錯了總額。Bob 在 PR 階段、4 分鐘內、context 還熱的時候就發現這個 bug,而不是隔天早上看到夜間 build 的告警。
漏網的情況怎麼辦
當天另一個 PR,工程師 Carol 改了一個冷門的 src/admin/audit_log.py。這個檔案三個月只被改過 2 次,Launchable 對它的歷史資料很少。模型給的子集只有 5 個測試,5 分鐘跑完都過,Carol merge 進 main。
當晚 defensive run(完整 1,630 個測試)發現,Carol 的改動意外影響了 test_admin_dashboard_export。這個測試從未被 audit_log 弄壞過,但這次因為共用一個 logger 設定而壞了。
幾件事同時發生:
- 告警在隔天早上發給 Carol(比舊流程多花 10 小時,但仍在合理範圍)
- 這次 (audit_log.py 改動 + admin_dashboard_export 失敗) 的事件被記錄到訓練資料
- 下次再有人改 audit_log.py,模型會把 admin_dashboard_export 加入推薦子集
模型從漏網事件中學習,這是 PTS 設計的核心。沒有 defensive run 就沒有持續學習的閉環。
三個月後的成效
團隊回顧導入三個月的數據:
PR 階段平均 CI 時間:15 分鐘 → 4.5 分鐘(減少 70%)
整合 bug 平均發現時間:14 小時 → 7 分鐘
CI 硬體成本:下降約 35%
Defensive run 抓到的漏網 bug:占同期所有 bug 的 4%(可接受)
但團隊認為更大的價值在開發體驗:工程師回報「不再害怕推 commit」,因為快速回饋讓他們可以小步迭代。
七、導入時的常見陷阱
從這個範例可以看到幾個值得注意的細節,如果你在評估導入,這幾點要特別留意。
陷阱一:不要太早開始選擇子集
模型需要訓練資料,通常至少累積兩到四週的 build 資料才會穩定。太早啟用 subset 會得到不可信的結果,反而打擊團隊信心。前期先讓 Launchable 觀察就好。
陷阱二:Defensive run 不能省
這個前面已經強調過,但值得再說一次。失去 defensive run 就失去了模型學習的閉環,長期模型會退化。建議至少每晚跑一次完整套件,結果回流訓練。
陷阱三:Confidence 參數要根據場景調
PR 階段(快速回饋優先)用 85-90%,merge 前的最後關卡用 99%,熱修補用 70%(犧牲信心換速度)。把這當成一個可調旋鈕而非單一設定。
陷阱四:Flaky 測試會嚴重影響效果
如果你的測試套件有大量 flaky 測試,先處理這個問題再導入 PTS,否則模型訓練資料污染嚴重,模型會把雜訊當訊號。
陷阱五:小型團隊或測試套件少於 200 個測試的專案,效益不明顯
PTS 是為了解決規模問題。如果測試套件本來就 5 分鐘跑完,沒必要用機器學習選測試。Launchable 通常在測試套件 30 分鐘以上才開始顯出顯著價值。
八、它不是萬能的:這些情境效果有限
公平起見,有幾類情境 PTS 效果有限,值得明說。
- 全新專案前幾個月沒有歷史資料,模型還在學,效益遲遲出不來。這時候靜態方法或人工標籤反而更實用。
- 測試本身設計不佳的情境(例如測試之間有強依賴、執行順序敏感),PTS 抓不到這類結構性問題,反而可能放大它們。
- 程式碼大規模重構會讓歷史資料瞬間失效。如果你剛搬完一次大重構,模型需要重新累積資料,過渡期可能要回去跑全套。
- 測試套件結構性失衡 — 例如單元測試太少、E2E 太多 — 不是 PTS 能修的問題。它能讓你跑得更聰明,但無法替你補上漏掉的測試覆蓋。
九、與其他工具的關係
最後釐清一些常見混淆。
Launchable 不是取代 pytest、JUnit、Cypress 這些測試框架,它是包裝在這些框架外面的決策層。你的測試還是用原本的 runner 跑,Launchable 只決定「這次跑哪些」。
它不是和 Selenium、Playwright 之類 UI 自動化工具競爭,反而和它們互補,因為 UI 測試特別慢,正是 PTS 最有價值的應用場景。
它和 Meta 的 TestGen-LLM / ACH 是不同方向的工具:
Launchable 解「該跑哪些測試」
TestGen-LLM 解「該寫哪些測試」
兩者可以同時用,實際上很多團隊正在這麼做 — 用 LLM 增加測試覆蓋,用 PTS 在大規模測試套件中快速選最相關的跑。
十、結論
Launchable 不是黑魔法,核心是兩個簡單觀察:
- 測試失敗有強烈的時空局部性
- 歷史資料比結構分析更能預測風險
從這兩個觀察出發,用 GBDT 模型量化條件機率,再包成可整合進任何 CI 的 SaaS,就是 Launchable 的全貌。
對於測試套件已經膨脹到 30 分鐘以上、團隊規模超過 20 人、且有持續累積的測試歷史的工程組織,導入 PTS 是少數能同時改善開發體驗、減少 CI 成本、提早發現 bug 的工具。它不取代好的測試設計,但能讓既有測試發揮最大價值。
它最動人的特性其實不是「省了多少時間」這個數字,而是它改變了團隊和測試的關係 — 從「測試是阻礙我推 commit 的關卡」變回「測試是幫我快速確認改動正確的工具」。這種文化轉變,才是長期最值得投資的地方。
資料來源
- Launchable 官方:Predictive Test Selection 產品說明 — launchableinc.com/predictive-test-selection/
- Launchable Docs:CLI 整合與 confidence 機制 — docs.launchableinc.com/actions/predictive-test-selection/faq
- Meta Engineering 部落格:Predictive Test Selection 原始介紹(2018) — engineering.fb.com/2018/11/21/developer-tools/predictive-test-selection/
- Meta 原始學術論文(arXiv 1810.05286):模型架構與 GBDT 選用理由
- BMW 客戶案例:工業場景的真實導入經驗 — launchableinc.com/customers/bmw-uses-launchable-to-optimize-testing-and-reduce-costs/
- BrowserStack 解析:第三方對 PTS 的整理 — browserstack.com/guide/predictive-test-selection
發表迴響