「這兩個 CI workflow 檔案,設定內容有一大半重複,直接合併成一個不就好了?」
聽起來是個顯而易見的重構——但「看起來重複」跟「真的可以合併」中間,藏著一個很容易被忽略的細節:兩份設定重複的部分,是不是真的完全等價,還是只是「表面像,實際上覆蓋範圍不一樣」。今天用 PHPUnit & Pest Test Explorer 這個專案裡一次真實發生過的 CI workflow 重構,把這個細節攤開來看。
PHPUnit & Pest Test Explorer 這個專案原本有兩份獨立的 GitHub Actions workflow:tests.yml 跑一般測試(涵蓋一個 OS × PHP 版本的矩陣),e2e.yml 專門跑端對端測試。兩份 workflow 各自都要做同樣的前置工作——checkout 原始碼、裝 PHP、裝 pnpm/node、裝 composer 的測試替身套件——只是最後跑的指令不一樣。
e2e.yml 有個特別之處:它雖然自己開了一個 3-OS 的矩陣(在三種作業系統上各跑一次),但實際上只鎖定安裝了一個特定的 PHP 版本(8.4)對應的測試框架替身套件。也就是說,e2e.yml 表面上是「跑在 3 種作業系統上」,但只驗證過「PHP 8.4 這一種版本組合」在這三個作業系統上的行為。
這次重構後來合併為 PR #424,真實的改動是:把 e2e 測試步驟,直接掛進 tests.yml 既有的「作業系統 × PHP 版本」矩陣裡,但只掛在 php == '8.4' 這一個維度上——也就是說,e2e 步驟一樣只在三種作業系統各跑一次(跟原本的行為一致),只是不再需要一份獨立的 workflow 檔案去重複那些前置設定。e2e.yml 整份刪除。
這個改動讓 CI 的總 job 數從 12 個降到 9 個——不是靠減少測試涵蓋範圍換來的,是靠把重複的前置設定收斂掉換來的。
用一組對照來看這個差異:
❌ 重構前:兩份 workflow 各自完整
tests.yml:OS × PHP 版本矩陣,跑一般測試
e2e.yml:獨立的 3-OS 矩陣,各自重跑一次 checkout/裝 PHP/裝 pnpm/裝套件,
只為了跑 e2e 測試(而且只驗證一種 PHP 版本組合)
→ 前置設定重複了兩次,維護時兩份都要同步改
✅ 重構後:e2e 併入既有矩陣的其中一個維度
tests.yml:OS × PHP 版本矩陣,一般測試照舊;
e2e 測試步驟掛在 php == '8.4' 這個維度上,
同樣是 3 個作業系統各跑一次
→ 前置設定只維護一份,總 job 數從 12 降到 9,
e2e 涵蓋的作業系統範圍沒有縮小
這個案例裡最值得注意的,不是「重構完成、job 數變少」這件事本身,而是 PR 描述裡明確記錄下來的一個發現:合併之後,php == '8.4' 這個維度裝的測試框架替身套件,跟原本 e2e.yml 裝的其實不完全一樣。
原因是:tests.yml 的 php-8.4 這個維度,為了跑一般測試,會裝所有相容的測試框架替身版本(不只一個),而系統設定裡「挑第一個偵測到的版本」這個邏輯,挑出來的版本跟原本 e2e.yml 裡刻意只裝的版本不一樣——結果是合併之後,e2e 測試實際驗證的框架版本組合,悄悄從原本刻意鎖定的版本,換成了另一個版本。
這正是這類「看起來只是搬個位置」的 CI 重構最容易踩的坑:兩個 job 表面上「涵蓋同一種東西」,但因為底層的環境設定邏輯不完全相同,合併之後涵蓋的實際範圍可能悄悄變了,而且不會有任何錯誤訊息告訴你這件事——CI 一樣會綠燈,只是綠燈驗證的東西換了。
值得注意的是,這個發現不是重構完成後才被動抓到的,而是在 PR 描述裡就主動標註出來——先誠實記錄「這裡的涵蓋範圍可能因為這次改動而變化」,再留給後續決定要不要修正,而不是悄悄合併、假裝什麼都沒變。
回想你手上維護過的 CI 設定:如果要合併兩份「看起來重複」的 workflow,你會怎麼確認合併後兩邊涵蓋的環境組合真的完全一致?還是只看「合併後 CI 有沒有變紅」就判斷沒問題?
明天要換一個場景:從一個真實 issue 回報開始,走一次「定位問題根因」的完整除錯過程。