開始使用 iOS 遊戲迴圈測試

透過遊戲迴圈測試,您可以編寫遊戲引擎原生的測試,然後在所選裝置上透過 Test Lab 執行測試。這樣一來,您就不必擔心要為不同的 UI 或測試架構編寫程式碼。遊戲迴圈測試會模擬真實玩家的動作,在 Test Lab 上執行測試時,可快速且大規模地驗證遊戲是否能為使用者提供良好的效能體驗。

本頁面說明如何執行遊戲迴圈測試,然後在 Firebase 控制台的「Test Lab」頁面中查看及管理測試結果。您也可以使用選用功能進一步自訂測試,例如撰寫自訂測試結果提早結束測試

什麼是遊戲迴圈測試?

迴圈是指在遊戲應用程式中完整或部分執行測試。您可以在模擬器上或 Test Lab 中的一組裝置上,在本機執行遊戲迴圈測試。遊戲迴圈測試的用途如下:

  • 以使用者身分執行遊戲。您可以編寫使用者輸入內容的指令碼、讓使用者處於閒置狀態,或以 AI 取代使用者 (例如,如果您在賽車遊戲中導入 AI,可以讓 AI 駕駛人負責使用者的輸入內容)。

  • 以最高畫質設定執行遊戲,即可瞭解哪些裝置支援這項設定。

  • 執行技術測試,例如編譯多個著色器、執行這些著色器,並檢查輸出內容是否符合預期。

步驟 1:註冊 Test Lab 的自訂網址配置

首先,您必須在應用程式中註冊 Firebase Test Lab 的自訂網址架構:

  1. 在 Xcode 中選取專案目標。

  2. 按一下「資訊」分頁,然後新增網址類型

  3. 在「URL Schemes」(網址架構) 欄位中,輸入 firebase-game-loop。 您也可以將自訂網址架構新增至專案的 Info.plist 設定檔 (位於 <dict> 標記內的任何位置),藉此註冊自訂網址架構:

    <key>CFBundleURLTypes</key>
     <array>
         <dict>
             <key>CFBundleURLName</key>
             <string></string>
             <key>CFBundleTypeRole</key>
             <string>Editor</string>
             <key>CFBundleURLSchemes</key>
             <array>
                 <string>firebase-game-loop</string>
             </array>
         </dict>
     </array>
    

應用程式現已設定完畢,可使用 Test Lab 執行測試。

步驟 2 (選用):設定應用程式以執行多個迴圈

如果應用程式已註冊多個自訂網址通訊協定,且您打算在測試中執行多個迴圈 (又稱情境),則必須在啟動應用程式時指定要執行的迴圈。

在應用程式委派中,覆寫 application(_:open:options:) 方法:

SwiftObjective-C
func application(_app: UIApplication,
                 open url: URL
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    let components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
    if components.scheme == "firebase-game-loop" {
        // ...Enter Game Loop Test logic to override application(_:open:options:).
    }
    return true
}
- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary &lt;UIApplicationOpenURLOptionsKey, id&gt; *)options {
  if ([url.scheme isEqualToString:(@"firebase-game-loop")]) {
      // ...Enter Game Loop Test logic to override application(_:open:options:).
  }
}

在測試中執行多個迴圈時,系統會將目前的迴圈當做參數傳遞至用於啟動應用程式的網址。您也可以剖析用於擷取自訂網址配置的 URLComponents 物件,取得目前的迴圈編號:

SwiftObjective-C
if components.scheme == "firebase-game-loop" {
    // Iterate over all parameters and find the one with the key "scenario".
    let scenarioNum = Int(components.queryItems!.first(where: { $0.name == "scenario" })!.value!)!
    // ...Write logic specific to the current loop (scenarioNum).
}
if ([url.scheme isEqualToString:(@"firebase-game-loop")]) {
    // Launch the app as part of a game loop.
    NSURLComponents *components = [NSURLComponents componentsWithURL:url
                                             resolvingAgainstBaseURL:YES];
    for (NSURLQueryItem *item in [components queryItems]) {
        if ([item.name isEqualToString:@"scenario"]) {
            NSInteger scenarioNum = [item.value integerValue];
            // ...Write logic specific to the current loop (scenarioNum).
        }
    }
}

步驟 3:建立及執行測試

註冊自訂 URL 配置後,您可以在 Firebase 控制台或使用 gcloud beta CLI 執行測試。Test Lab如果尚未產生應用程式的 IPA 檔案,請先產生 (稍後需要找到該檔案)。

  1. 在 Xcode 中,選取目標應用程式的佈建設定檔。
  2. 在隨即顯示的下拉式選單中,依序按一下「產品」>「封存」。 選取最新的封存檔,然後按一下「發布應用程式」
  3. 在隨即顯示的視窗中,依序按一下「開發」>「下一步」
  4. 選用:如要加快建構速度,請取消選取「從 Bitcode 重建」選項,然後按一下「下一步」Test Lab 不需要縮減或重建應用程式即可執行測試,因此您可以安全地停用這個選項。
  5. 按一下「Export」(匯出),然後輸入要下載應用程式 IPA 檔案的目錄。

Firebase 控制台中執行測試

  1. 開啟 Firebase 控制台 (如果尚未開啟),然後建立專案。

  2. Firebase 控制台的 Test Lab 頁面中,按一下「執行您的第一項測試」>「執行 iOS 遊戲迴圈」

  3. 在「Upload App」(上傳應用程式) 專區中,按一下「Browse」(瀏覽),然後選取應用程式的 IPA 檔案 (如果尚未為應用程式產生 IPA 檔案,請先執行這項操作)。

  4. 選用:如要一次執行多個迴圈 (即情境),或選取要執行的特定迴圈,請在「情境」欄位中輸入迴圈編號。

    舉例來說,如果輸入「1-3, 5」,Test Lab 會執行第 1、2、3 和 5 圈的迴圈。 根據預設 (如果您未在「情境」欄位中輸入任何內容), Test Lab 只會執行迴圈 1。

  5. 在「裝置」部分,選取要用來測試應用程式的一或多個實體裝置,然後按一下「開始測試」

使用 gcloud beta CLI 執行測試

  1. 如果尚未設定本機 gcloud SDK 環境,請先完成設定,然後務必安裝 gcloud beta 元件

  2. 執行 gcloud beta firebase test ios run 指令,並使用下列旗標設定執行作業:

遊戲迴圈測試的旗標
--type

必要:指定要執行的 iOS 測試類型。您可以輸入測試類型 xctest (預設) 或 game-loop

--app

必要:應用程式 IPA 檔案的絕對路徑 (Google Cloud Storage 或檔案系統)。這個標記僅在執行遊戲迴圈測試時有效。

--scenario-numbers

要在應用程式中執行的迴圈 (又稱情境)。 您可以輸入一個迴圈、迴圈清單或迴圈範圍。預設迴圈為 1。

舉例來說,--scenario-numbers=1-3,5 會執行迴圈 1、2、3 和 5。

--device-model

要用來執行測試的實體裝置 (請參閱這篇文章,瞭解可用的裝置)。

--timeout

測試執行的時間長度上限。您可以輸入整數來表示時間長度 (以秒為單位),也可以輸入整數和列舉來表示時間長度 (以較長的時間單位為準)。

例如:

  • --timeout=200 會在測試執行 200 秒後強制終止測試。
  • --timeout=1h 會在測試執行一小時後強制終止測試。

舉例來說,下列指令會執行遊戲迴圈測試,在 iPhone 8 Plus 上執行迴圈 1、4、6、7 和 8:

gcloud beta firebase test ios run
 --type game-loop --app path/to/my/App.ipa --scenario-numbers 1,4,6-8
 --device-model=iphone8plus

如要進一步瞭解 gcloud CLI,請參閱參考說明文件

在本機執行測試

如要在本機執行測試,請在模擬器中載入遊戲應用程式,然後執行下列指令:

xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://
  • 執行 instruments -s devices 指令,即可找出模擬器的 UDID。

  • 如果只有一個模擬器正在執行,請輸入特殊字串 "booted",取代 SIMULATOR_UDID

如果測試包含多個迴圈,您可以將迴圈編號傳遞至 scenario 標記,指定要執行的迴圈。請注意,在本機執行測試時,一次只能執行一個迴圈。舉例來說,如要執行迴圈 1、2 和 5,必須為每個迴圈執行個別指令:

xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://?scenario=1
xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://?scenario=2
xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://?scenario=5

提早結束測試

根據預設,即使所有迴圈都已執行完畢,遊戲迴圈測試仍會持續執行,直到達到五分鐘的逾時時間為止。達到逾時時間後,測試就會結束,並取消所有待處理的迴圈。如要加快測試速度或提早結束測試,請在應用程式的 AppDelegate 中呼叫Test Lab的自訂網址架構firebase-game-loop-complete。例如:

SwiftObjective-C
/// End the loop by calling our custom url scheme.
func finishLoop() {
    let url = URL(string: "firebase-game-loop-complete://")!
    UIApplication.shared.open(url)
}
- (void)finishLoop {
  UIApplication *app = [UIApplication sharedApplication];
  [app openURL:[NSURL URLWithString:@"firebase-game-loop-complete://"]
      options:@{}
completionHandler:^(BOOL success) {}];
}

遊戲迴圈測試會終止目前的迴圈,並執行下一個迴圈。 如果沒有要執行的迴圈,測試就會結束。

撰寫自訂測試結果

您可以設定遊戲迴圈測試,將自訂測試結果寫入裝置的檔案系統。這樣一來,測試開始執行時,Test Lab 會將結果檔案儲存在測試裝置的 GameLoopsResults 目錄中 (您必須自行建立該目錄)。測試結束時,Test Lab 會將 GameLoopResults 目錄中的所有檔案移至專案的 bucket。設定測試時,請注意下列事項:

  • 無論檔案類型、大小或數量為何,所有結果檔案都會上傳。

  • Test Lab 會等到測試中的所有迴圈都執行完畢,才會處理測試結果。因此,如果測試包含多個會寫入輸出的迴圈,請務必將輸出內容附加至不重複的結果檔案,或為每個迴圈建立結果檔案。這樣就能避免覆寫先前迴圈的結果。

如要設定測試來編寫自訂測試結果,請按照下列步驟操作:

  1. 在應用程式的 Documents 目錄中,建立名為 GameLoopResults 的目錄。

  2. 在應用程式程式碼中的任意位置 (例如應用程式委派),加入下列內容:

    SwiftObjective-C
    /// Write to a results file.
    func writeResults() {
      let text = "Greetings from game loops!"
      let fileName = "results.txt"
      let fileManager = FileManager.default
      do {
    
      let docs = try fileManager.url(for: .documentDirectory,
                                     in: .userDomainMask,
                                     appropriateFor: nil,
                                     create: true)
      let resultsDir = docs.appendingPathComponent("GameLoopResults")
      try fileManager.createDirectory(
          at: resultsDir,
          withIntermediateDirectories: true,
          attributes: nil)
      let fileURL = resultsDir.appendingPathComponent(fileName)
      try text.write(to: fileURL, atomically: false, encoding: .utf8)
      } catch {
        // ...Handle error writing to file.
      }
    }
    
    /// Write to a results file.
    - (void)writeResults:(NSString *)message {
        // Locate and create the results directory (if it doesn't exist already).
        NSFileManager *manager = [NSFileManager defaultManager];
        NSURL* url = [[manager URLsForDirectory:NSDocumentDirectory
                                      inDomains:NSUserDomainMask] lastObject];
        NSURL* resultsDir = [url URLByAppendingPathComponent:@"GameLoopResults"
                                                 isDirectory:YES];
        [manager createDirectoryAtURL:resultsDir
          withIntermediateDirectories:NO
                           attributes:nil
                                error:nil];
    
        // Write the result message to a text file.
        NSURL* resultFile = [resultsDir URLByAppendingPathComponent:@"result.txt"];
        if ([manager fileExistsAtPath:[resultFile path]]) {
            // Append to the existing file
            NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:resultFile
                                                                     error:nil];
            [handle seekToEndOfFile];
            [handle writeData:[message dataUsingEncoding:NSUTF8StringEncoding]];
            [handle closeFile];
        } else {
            // Create and write to the file.
            [message writeToURL:resultFile
                     atomically:NO
                       encoding:NSUTF8StringEncoding error:nil];
        }
    }