商品列表加入漸層、圖片解碼或複雜陰影後,功能測試可能仍然全部通過,捲動卻開始掉幀。只靠人工拖動幾次,很難判斷這是偶發抖動還是程式碼回歸。更可靠的做法,是在雲端 Mac 上固定模擬器、資料集與手勢,使用 XCTMetric 持續採樣耗時、CPU 與記憶體,再讓持續整合只攔截超出基準的變化。
先固定會影響結果的變數
建立效能門檻的第一步不是撰寫斷言,而是降低環境雜訊。固定 Xcode 與 macOS 版本、模擬器型號、系統語言、顯示方向和測試資料。測試帳號應直接進入目標頁面,避免登入請求、圖片下載與伺服器回應時間混入捲動指標。
為應用程式加入只在 UI 測試期間啟用的啟動參數,例如 -UITestSeed fixed。它應載入本機測試資料,產生數量固定、順序固定的列表項目,並停用隨機動畫。列表還需要穩定的輔助使用識別碼,例如 feed.list 和 feed.reset;測試程式碼不應依賴本地化文字來尋找元素。
首次執行通常包含應用程式安裝、動態函式庫載入、字型初始化和快取預熱。正式採樣前,應先執行一次不計分的測試,但不要透過刪除快取來刻意製造不符合日常使用情境的「完全冷啟動」環境。
效能基準描述的是一套完整環境,而不是一個能套用到所有 Mac、所有模擬器的通用毫秒數。
建立可重複的捲動路徑
單次 swipeUp() 太容易受到起點和列表長度影響。更可靠的測試應先回到頂端,確認第一個錨點可見,再執行固定次數的手勢。每輪量測結束後都要再次重設,避免下一輪從列表底部開始。
import XCTest
final class FeedScrollPerformanceTests: XCTestCase {
func testFeedScrollPerformance() {
let app = XCUIApplication()
app.launchArguments += ["-UITestSeed", "fixed"]
app.launch()
let list = app.collectionViews["feed.list"]
XCTAssertTrue(list.waitForExistence(timeout: 10))
app.buttons["feed.reset"].tap()
XCTAssertTrue(app.cells["feed.item.0"].waitForExistence(timeout: 5))
let options = XCTMeasureOptions()
options.iterationCount = 5
measure(
metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()],
options: options
) {
app.buttons["feed.reset"].tap()
for _ in 0..<6 {
list.swipeUp(velocity: .fast)
}
}
}
}
重設按鈕可以只在測試模式下顯示,但它必須執行確定性的捲動,而不是重新請求資料。如果頁面採用分頁載入,應將網路層替換為本機回應,或明確將分頁耗時拆分成另一項測試。
在固定目的地執行並保存結果
先列出可用的模擬器,確認持續整合沒有在未提示的情況下切換裝置。接著為測試指定唯一的結果套件路徑;舊的結果套件必須事先刪除,否則 xcodebuild 會拒絕覆寫。
set -euo pipefail
RESULT="$PWD/artifacts/FeedScroll.xcresult"
rm -rf "$RESULT"
mkdir -p "$PWD/artifacts"
xcodebuild test \
-workspace App.xcworkspace \
-scheme AppUITests \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-only-testing:AppUITests/FeedScrollPerformanceTests \
-resultBundlePath "$RESULT"
OS=latest 適合始終跟隨目前工具鏈的流水線;若需要長期比較,應固定特定的執行階段,並在升級 Xcode 或系統後重新建立基準。OVPS 節點上的工作也應記錄主機設定、提交編號、Xcode 版本和模擬器執行階段,避免將環境升級誤判為程式碼效能退化。
使用目前 Xcode 隨附的 xcresulttool 前,應先查看說明,因為不同工具鏈的子命令可能有所調整。無論使用指令碼還是測試報告解析器,原始 xcresult 都應在失敗時保留為附件。
使用中位數和相對門檻判定
不要因為一次較慢的樣本就阻止合併。每次提交執行五到七輪,先排除預熱輪,再計算中位數。基準可以採用主分支近期多次穩定執行的中位數,而不是永久保留第一次的量測值。
建議分別觀察三類變化:總耗時反映使用者的等待時間;CPU 指標容易揭露重複版面配置或過度繪製;記憶體指標則可發現圖片快取和檢視重複使用方面的問題。如果某次提交的耗時增加,但 CPU 與記憶體保持穩定,應先檢查模擬器負載;只有三項同時惡化時,才更像是應用程式碼回歸。
門檻應採用比例,並設定最小絕對差。例如,只有當中位耗時相較基準增加超過 12%,而且絕對增幅超過團隊定義的雜訊下限時,測試才判定失敗。門檻數值必須依據相同環境中的歷史波動設定,不能直接照搬其他專案。
失敗後保留證據並縮小範圍
效能門檻失敗時,不要立即覆寫基準。先用相同提交重新執行一次;如果仍然失敗,應保存 xcresult、完整日誌、測試資料版本、系統與工具鏈版本,以及 CPU、記憶體和耗時摘要。接著縮小提交範圍,重點檢查圖片解碼、陰影與模糊效果、自動版面配置約束、主執行緒同步 I/O、列表重複使用和日誌輸出。
還要區分「指標退化」與「測試路徑變更」。如果輔助使用識別碼失效,或重設動作沒有回到頂端,測試可能仍可執行,卻量測了不同的頁面區域。為關鍵起點和終點加入可見性斷言,會比單純增加重試次數更有效。
基準更新應作為獨立變更提交,說明環境升級或可接受的互動調整,並附上更新前後的樣本。如此一來,效能門檻既不會因偶發雜訊而頻繁觸發,也不會因隨意更新基準而失去作用。
常見問題
捲動效能門檻適合使用固定毫秒數嗎?
不適合直接跨環境共用。應固定主機、系統、模擬器和測試資料,以近期穩定樣本的中位數建立相對門檻。
為什麼第一次執行不應納入基準?
第一次可能包含安裝、動態函式庫載入、字型初始化與快取預熱。先執行一次不計分的暖機測試,可降低雜訊。
效能測試失敗後應保存哪些資料?
至少保存 xcresult、測試日誌、提交版本、Xcode 與系統版本、模擬器型號、測試資料版本,以及 CPU 和記憶體指標。
將下一次建置部署至獨享物理節點
從三種 Apple Silicon 配置與六個在售節點中選擇,實際可用狀態以控制台即時回傳為準。