接進 CI:讓 App 自動化變回歸防線
#CI #GitHub Actions #DevOps #QA 實戰
本機跑得過的測試,只有你一個人知道。接進 CI 之後,每一次 push 都有人幫你守門,這是自動化該走到的地方。 這篇前半段講一般的做法,後半段講我自己的 CI。老實說,後半段沒有前半段那麼漂亮。 CI 的目標 把自動化變回歸防線,流程要做到四件事: 1. push / PR 觸發 :程式碼一進去就跑,不等人手動。 2. 跑測試、輸出結果 :失敗要可讀,ReportPortal 接收報告、標記失敗項目。 3. 不達標擋合併 :GitHub branch protection 設 required status checks,紅燈就不讓 merge。 4. 結果可追溯 :artifact 存測試截圖、log,事後查問題有憑據。 沒有第三點,自動化只是跑著看的儀表板,不是防線。 GitHub Actions 跑 Appium 的現實 Android:可以跑,但要額外設定 GitHub hosted runner(ubuntu latest)沒有內建 Android emulator,要靠 reactivecircus/android emulator runner Action 建立 AVD、啟動 emulator、等待 boot 完成。 Android workflow 骨架: reactivecircus/android emulator runner 會啟動 emulator 再執行 script;emulator ready 之前它會自動等待,不需要自己寫 polling。 iOS:hosted runner 不理想 GitHub hosted macOS runner 有 Xcode,但 iOS Simulator 在虛擬化環境跑不穩,session 建立慢、偶有 kernel panic。實務上有兩條路: | 方案 | 特點 | | | | | Self hosted Mac runner | 接一台實體 Mac Mini 當 runner,環境完全自控,成本一次性 | | 雲端設備農場 | BrowserStack / Sauce Labs 在 CI 跑真機,USE CLOUD=1 一個環境變數切換 | iOS 測試建議:PR 階段跑 Android emulator smoke 擋門,iOS 完整回歸改排程跑在 self hosted 或雲端農場,不用等 hosted runner。 策略:哪些測試進 CI 不是所有測試都應該進 PR gate。測試進 CI 的原則是 速度換覆蓋的取捨 : gate 測試要夠快、夠穩。一旦開發者開始忽略紅燈,防線就形同虛設。 我的 CI 實際長這樣 我的情況跟上面的範本不太一樣。測試跑在自己接的實體裝置上,runner 是 self hosted,觸發點也不是測試專案的 PR,而是 App 出新版本 :App 那邊 build 完,會送一個事件過來,帶著版本號、平台和安裝包類型,自動化就開始跑。 整條 e2e workflow 大概是這樣: 有幾個設計是我覺得值得分享的: 跑測試前先救援。 self hosted runner 跑久了一定會壞:UiAutomator2 卡死、裝置沒回應、iOS 的 companion 服務沒起來。所以每次跑之前,Android 先自動清 UiAutomator2 殘留、重啟 adb、確認裝置可用;iOS 先確認 companion 服務正常。與其等測試失敗再查,不如開跑前先把已知的問題清掉。 擋發版的是裁決,不是通過率。 ReportPortal 那篇講過:通過率低只發警告,有產品 bug 才讓 workflow 失敗。 失敗時自動抓證據。 測試失敗時,workflow 會抓裝置的 bugreport,比對 Crashlytics 找有沒有對應的 crash。開 bug 單的時候,證據已經在那裡了。 PR 上的 lint 刻意保守。 測試專案自己的 PR 只跑兩道檢查:一道是 ruff,只開語法錯誤、未定義名稱、未使用的 import 這類規則,只擋真的 bug,不擋風格;另一道是等待策略那篇講的 sleep 檢查,只看新增的行。規則開太多,每個 PR 都在修風格,最後大家就會想辦法繞過它。 我把它降級了 上面是設計好的完整流程。但現在實際在跑的,是它的降級版:App 出新版時,送來的事件只會觸發「上傳安裝包」,不會自動跑測試。要跑自動化,我從 GitHub Actions 手動觸發。 這是我自己的決定。完整的自動觸發需要足夠的裝置隨時待命,每一輪跑完還要有人看結果、處理失敗。以我一個人維護的狀況,撐不起每個版本都全自動跑一輪、每一輪都有人認真看。與其讓它自動跑,然後結果沒人理,我寧可在我有空看的時候手動跑。 我沒有把完整流程刪掉。恢復的步驟我寫成一份 checklist:把觸發事件接回 e2e workflow、停掉只上傳安裝包的那條、上游完全不用改、再完整驗證一次。等條件夠了,改回來只是幾行設定。 我覺得這比硬撐好。自動化最怕的不是跑得少,而是跑了沒人信、紅了沒人看。 報表捏造了「幾乎全過」 最後一個 CI 相關的事故,也是我覺得最值得警惕的一個。 我們的測試清單當時散在好幾個地方:依序執行核心測試的腳本、計算測試數量的腳本、分組統計的腳本,各自維護一份檔名清單。有一次我把一支測試檔改了名,只改到其中一部分。 結果是那一輪直接出現 USAGE ERROR,所有測試都沒跑起來。但更糟的是,負責產生摘要的邏輯在這種情況下,居然產生了一份通過率很高的結果。所有測試都沒跑,報表卻說幾乎全過。 摘要邏輯的問題我當下就修掉了。但根本原因是清單散在好幾個地方,這件事我列成最優先,老實說還沒做完: 測試清單只能有一份。 規劃是改成一份 manifest 檔,列出每支測試檔、對應的 marker 和分組,所有腳本都讀這一份。改名或增刪測試,只要動一個地方。 清單要有檢查。 CI 裡加一道檢查:manifest 裡的每個檔案都必須存在。路徑錯了,在 PR 階段就擋下來,不會等到裝置上才炸。 排在最優先卻還沒做完,聽起來有點矛盾。但這就是一個人維護的日常:每一版 App 更新都有測試要修,「修眼前壞掉的」永遠比「讓它以後不容易壞」更急。我把它寫出來,一方面提醒自己,一方面也想告訴你:如果你的框架還小,現在就把清單收成一份,比事故之後再補便宜太多。 報表是整個自動化裡最被信任的東西,所以它出錯的代價最大。一份會說謊的報表,比沒有報表更危險。 收尾:QA 在 DevOps 的角色 自動化測試是品質的安全網,不是取代人的判斷。它能擋住「昨天還好的功能今天壞了」這類回歸,但它擋不住「需求本身就錯了」或「邊界情境沒想到」。 QA 在 DevOps 裡的價值在三件事: 設計這張網 :決定哪些場景值得自動化、怎麼分層、怎麼擺進 CI。 讀懂它的訊號 :失敗是 bug 還是 flaky?報告在說什麼?該升級優先級還是先隔離? 推動可測性 :跟開發討論 testability,讓 App 本身好測,而不是靠硬撬。 這個系列從第一個 assert 開始,走過定位策略、WebView、POM 設計、fixture、parametrize、等待與 flaky、報告,再到雲端真機,最後把整套框架接進 CI。 回頭看我自己這一路:公司要導入,從零開始,第一支寫登入,然後是一段一直 flaky、進度追不上的日子。後來跟主管把目標改成「先求穩定」,很多決定都是從這四個字長出來的:不為了數字補測試、不為了速度上平行、CI 寧可降級也不要跑了沒人看。 這套框架不是範本裡那種完整的樣子,但它是一個人扛得動、跑出來的結果大家願意相信的樣子。對我來說,這比漂亮更重要。