iOS 用ゲームループ テストを使ってみる

ゲームループ テストを使用すると、ゲームエンジンにネイティブなテストを記述し、Test Lab を使用して特定のデバイスでテストを行うことができます。これにより、UI やテスト フレームワークの違いを気にせずにテストを実施できます。ゲームループ テストでは、実際のプレーヤーの動作をシミュレートします。Test Lab でテストを行うと、高速かつスケーラブルな方法でゲーム パフォーマンスを検証できます。

このページでは、ゲームループ テストを実施して、その結果を Firebase コンソールの [Test Lab] ページで確認し、管理する方法について説明します。カスタムテストの結果の書き込みテストの早期終了などのオプション機能でテストをカスタマイズすることもできます。

ゲームループ テストとは

ループは、ゲームアプリのテストの全体または一部を通しで実行します。ゲームループ テストは、ローカルのシミュレータで行うことも、Test Lab の一連のデバイスで行うこともできます。ゲームループ テストは次の目的で使用できます。

  • エンドユーザーがプレイするのと同じ方法でゲームを実行する。ユーザーの入力はスクリプト化できます。また、ユーザーをアイドル状態にすることもできます。AI を実装している場合は、ユーザーを AI で置き換えることも可能です(たとえば、カーレース ゲームでユーザーの入力の代わりに AI のドライバを配置できます)。

  • 最高品質の設定でゲームを実行し、デバイスの対応状況を確認する。

  • 技術的なテストを行う。たとえば、複数のシェーダをコンパイルして実行し、予期した結果が出力されるかどうか確認します。

ステップ 1: Test Lab のカスタム URL スキームを登録する

まず、Firebase Test Lab のカスタム URL スキームをアプリに登録する必要があります。

  1. Xcode でプロジェクト ターゲットを選択します。

  2. [Info] タブをクリックし、新しい URL タイプを追加します。

  3. [URL Schemes] フィールドに「firebase-game-loop」と入力します。また、<dict> タグ内の任意の場所で、プロジェクトの Info.plist 構成ファイルにカスタム URL スキームを追加することもできます。

    <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(省略可): 複数のループを実行するようにアプリを構成する

アプリに複数のカスタム URL スキームを登録し、テストで複数のループ(シナリオ)を実行する場合は、起動時にアプリで実行するループを指定する必要があります。

アプリのデリゲートで、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:).
  }
}

テストで複数のループを実行すると、アプリの起動に使用される URL に現在のループがパラメータとして渡されます。カスタム URL スキームの取得に使用される 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: テストを作成して実行する

Test Lab のカスタム URL スキームを登録したら、Firebase コンソールまたは gcloud beta CLI でテストを行います。アプリの IPA ファイルをまだ生成していない場合は、生成します(このファイルは後で必要になります)。

Firebase コンソールでテストを行う

  1. プロジェクトを作成していない場合は、Firebase コンソールを開いて作成します。

  2. Firebase コンソールの Test Lab ページで、[最初のテストの実行] > [iOS ゲームループ テストの実行] の順にクリックします。

  3. [アプリのアップロード] セクションで [参照] をクリックし、アプリの IPA ファイルを選択します(まだ作成していない場合は、アプリに IPA ファイルを生成します)。

  4. 省略可: 一度に複数のループ(シナリオ)を実行する場合や、特定のループを選択して実行する場合は、[シナリオ] フィールドにループ番号を入力します。

    たとえば、「1-3、5」と入力すると、Test Lab はループ 1、2、3、5 を実行します。デフォルト([シナリオ] フィールドに何も入力しない場合)では、Test Lab はループ 1 のみを実行します。

  5. [デバイス] セクションで、アプリをテストする物理デバイスを 1 つ以上選択し、[テストを開始] をクリックします。

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 つのループ、1 つまたは複数のループ、あるいはループの範囲を入力できます。デフォルトのループは 1 です。

たとえば、--scenario-numbers=1-3,5 はループ 1、2、3、5 を実行します。

--device-model

テストを行う実機(使用可能なデバイスで使用できるデバイスを確認してください)。

--timeout

テストの最大期間。期間を表す整数を秒単位で入力します。より長い時間を指定する場合は、整数と時間単位を入力します。

例:

  • --timeout=200 を指定した場合、開始から 200 秒後にテストを強制終了します。
  • --timeout=1h を指定した場合、開始から 1 時間後にテストを強制終了します。

次のコマンドでは、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://
  • シミュレータの UDID を確認するには、instruments -s devices コマンドを実行します。

  • 実行中のシミュレータが 1 つだけの場合は、SIMULATOR_UDID の代わりに特殊な文字列 "booted" を入力します。

テストに複数のループが含まれている場合は、ループ番号を scenario フラグに渡し、実行するループを指定できます。テストをローカルで行う場合、一度に実行できるループは 1 つだけです。たとえば、ループ 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

早期にテストを終了する

デフォルトでは、すべてのループが実行された場合でも、ゲームループ テストは 5 分間のタイムアウトに達するまで継続されます。タイムアウトに達すると、テストは終了し、保留中のループはすべてキャンセルされます。テストを高速化または早期に終了するには、アプリの AppDelegate で Test Lab のカスタム URL スキーム 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 ディレクトリからプロジェクトのバケットに移動します。テストを設定する際は、次の点に注意してください。

  • ファイル形式、サイズ、数量に関係なく、すべての結果ファイルがアップロードされます。

  • Test Lab は、テストのすべてのループの実行が終了するまでテスト結果を処理しません。出力を書き込むループが複数ある場合は、各ループの出力が 1 つの結果ファイルに追加されるようにするか、ループごとに結果ファイルが作成されるようにする必要があります。これにより、前のループの結果が上書きされるのを防止できます。

カスタムテストの結果を書き込むようにテストを設定するには:

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