開始使用 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:) 方法:

Swift

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
}

Objective-C

- (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 物件,取得目前的迴圈編號:

Swift

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).
}

Objective-C

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 配置 Test Lab 後,即可在 Firebase 控制台或使用 gcloud beta CLI 執行測試。如果尚未產生應用程式的 IPA 檔案,請先產生 (稍後需要找到該檔案)。

Firebase 控制台中執行測試

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

  2. Firebase 控制台的 Test Lab 頁面中,按一下「Run Your First Test」(執行您的第一項測試) >「Run an iOS Game Loop」(執行 iOS 遊戲迴圈)

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

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

    舉例來說,輸入「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,加快測試速度或提早結束測試。例如:

Swift

/// End the loop by calling our custom url scheme.
func finishLoop() {
    let url = URL(string: "firebase-game-loop-complete://")!
    UIApplication.shared.open(url)
}

Objective-C

- (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. 在應用程式程式碼中的任意位置 (例如應用程式委派),加入下列內容:

    Swift

    /// 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.
      }
    }
    

    Objective-C

    /// 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];
        }
    }