游戏循环测试入门

如果游戏应用基于不同的界面框架构建而成,则可能很难实现游戏测试的自动化。借助游戏循环 (Game Loop) 测试,您可以将原生测试与 Test Lab 相集成,并在选定设备上轻松运行这些测试。游戏循环测试会通过游戏应用来运行测试,同时模拟真实玩家的操作。本指南将向您介绍如何运行游戏循环测试,以及如何在 Firebase 控制台中查看和管理测试结果。

根据您所用的游戏引擎,您可以使用单个或多个循环实现测试。一个循环是指对您的游戏应用运行的一轮完整或部分测试。游戏循环可用于如下目的:

  • 以最终用户玩游戏的方式运行游戏的某一关卡。您可以通过脚本来模拟用户输入、让用户挂机,或者在符合游戏情景的情况下用 AI 来代替用户(例如,假设您有一款赛车游戏,并且已经实现了 AI,那么就可以轻松设置一位 AI 驾驶员来代替用户提供输入)。
  • 以最高画质设置运行游戏,看看设备能否支持这种设置。
  • 运行技术测试(编译并执行多个着色器,检查输出结果是否符合预期,等等)。

您可以使用单个测试设备、一组测试设备或 Test Lab 来运行游戏循环测试,但不建议使用虚拟设备运行游戏循环测试,因为它们的图形帧速率比实体设备要低。

准备工作

为实现测试,您必须先针对游戏循环测试配置应用。

  1. 在应用清单 (manifest) 中,为您的 activity 添加一个新的 intent 过滤器:

    <activity android:name=".MyActivity">
       <intent-filter>
           <action android:name="com.google.intent.action.TEST_LOOP"/>
           <category android:name="android.intent.category.DEFAULT"/>
           <data android:mimeType="application/javascript"/>
       </intent-filter>
       <intent-filter>
          ... (other intent filters here)
       </intent-filter>
    </activity>

    这样一来,Test Lab 就可以使用特定的 intent 触发该 activity,从而启动您的游戏。

  2. 在代码(建议在 onCreate 方法声明内)中添加以下内容:

    Kotlin

    val launchIntent = intent
    if (launchIntent.action == "com.google.intent.action.TEST_LOOP") {
        val scenario = launchIntent.getIntExtra("scenario", 0)
        // Code to handle your game loop here
    }

    Java

    Intent launchIntent = getIntent();
    if(launchIntent.getAction().equals("com.google.intent.action.TEST_LOOP")) {
        int scenario = launchIntent.getIntExtra("scenario", 0);
        // Code to handle your game loop here
    }

    这样可以让您的 activity 检查启动它的 intent。您也可以稍后(例如,在初次加载游戏引擎后)再添加此代码。

  3. 建议:在测试的末尾处添加如下内容:

    Kotlin

    yourActivity.finish()

    Java

    yourActivity.finish();

    这会在游戏循环测试完成后关闭您的应用。该测试依赖于应用的界面框架来启动下一次循环,关闭应用即表明测试已完成。

创建并运行游戏循环测试

为游戏循环测试配置好应用后,您可以立即创建测试并在游戏应用中运行该测试。您可以选择使用 Firebase 控制台gcloud 命令行界面 (CLI)Test Lab 中运行测试,也可以使用 Test Loop Manager 在本地设备上运行测试。

在本地设备上运行测试

Test LabTest Loop Manager 是一款开源应用,可帮助您集成游戏循环测试并在本地设备上运行这些测试。此外,它还可以让您的质量保证团队在其设备上运行相同的游戏循环。

如需使用 Test Loop Manager 在本地设备上运行测试,请执行以下操作:

  1. 在手机或平板电脑上下载 Test Loop Manager,然后运行以下命令安装该工具:
    adb install testloopmanager.apk
  2. 在您的手机或平板电脑上,打开 Test Loop Apps 应用。该应用会显示设备上可以通过游戏循环运行的应用列表。如果列表中未显示您的游戏应用,请确保您的 intent 过滤器与“准备工作”部分的第 1 步中描述的过滤器相匹配。
  3. 选择您的游戏应用,然后选择要运行的循环数量。 注意:在此步骤中,您可以选择运行多个循环构成的一个子集,而不是仅运行一个循环。如需详细了解如何同时运行多个循环,请参阅可选功能
  4. 点击运行测试。您的测试会立即开始运行。

Test Lab 中运行测试

您可以使用 Firebase 控制台gcloud CLITest Lab 中运行游戏循环测试。在开始之前,请先打开 Firebase 控制台并创建一个项目(如果尚未创建)。

使用 Firebase 控制台

  1. Firebase 控制台中,点击左侧面板中的 Test Lab
  2. 点击运行首个测试(如果您的项目以前运行过测试,则点击运行测试)。
  3. 选择游戏循环作为测试类型,然后点击继续
  4. 点击浏览,找到您的应用的 .apk 文件。 注意:在此步骤中,您可以选择运行多个循环构成的一个子集,而不是仅运行一个循环。如需详细了解如何同时运行多个循环,请参阅可选功能
  5. 点击继续
  6. 选择要用于测试您的应用的实体设备。
  7. 点击开始测试

如需详细了解如何使用 Firebase 控制台开始测试,请参阅开始使用 Firebase 控制台执行测试

使用 gcloud 命令行 (CLI)

  1. 如果尚未安装 Google Cloud SDK,请下载并安装它。

  2. 使用您的 Google 账号登录 gcloud CLI:

    gcloud auth login

  3. 在 gcloud 中设置您的 Firebase 项目,其中 PROJECT_ID 是您的 Firebase 项目的 ID:

    gcloud config set project PROJECT_ID
    
  4. 运行首个测试:

    gcloud firebase test android run \
     --type=game-loop --app=<var>path-to-apk</var> \
     --device model=herolte,version=23
    

如需详细了解如何使用 gcloud CLI 开始测试,请参阅通过 gcloud 命令行开始执行测试

可选功能

Test Lab 提供了一些可让您进一步自定义测试的可选功能,包括可以写入输出数据、支持多个游戏循环以及相关循环的标签。

写入输出数据

您的游戏循环测试可以将输出写入到 launchIntent.getData() 方法中指定的某个文件中。运行测试后,您可以在 Firebase 控制台的 Test Lab 部分中访问这些输出数据(请参阅游戏循环测试输出文件示例)。

Test Lab 遵循在应用之间共享文件的最佳实践(具体说明请参阅共享文件)。在您的 activity 的 onCreate() 方法(您的 intent 所在的位置)中,您可以运行如下代码来检查数据输出文件:

Kotlin

val launchIntent = intent
val logFile = launchIntent.data
logFile?.let {
    Log.i(TAG, "Log file ${it.encodedPath}")
    // ...
}

Java

Intent launchIntent = getIntent();
Uri logFile = launchIntent.getData();
if (logFile != null) {
    Log.i(TAG, "Log file " + logFile.getEncodedPath());
    // ...
}

如果要在游戏应用的 C++ 代码中向该文件写入内容,您可以传递文件描述符,而非文件路径:

Kotlin

val launchIntent = intent
val logFile = launchIntent.data
var fd = -1
logFile?.let {
    Log.i(TAG, "Log file ${it.encodedPath}")
    fd = try {
        contentResolver
            .openAssetFileDescriptor(logFile, "w")!!
            .parcelFileDescriptor
            .fd
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
        -1
    } catch (e: NullPointerException) {
        e.printStackTrace()
        -1
    }
}

// C++ code invoked here.
// native_function(fd);

Java

Intent launchIntent = getIntent();
Uri logFile = launchIntent.getData();
int fd = -1;
if (logFile != null) {
    Log.i(TAG, "Log file " + logFile.getEncodedPath());
    try {
        fd = getContentResolver()
                .openAssetFileDescriptor(logFile, "w")
                .getParcelFileDescriptor()
                .getFd();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        fd = -1;
    } catch (NullPointerException e) {
        e.printStackTrace();
        fd = -1;
    }
}

// C++ code invoked here.
// native_function(fd);

C++

#include <unistd.h>
JNIEXPORT void JNICALL
Java_my_package_name_MyActivity_native_function(JNIEnv *env, jclass type, jint log_file_descriptor) {
// The file descriptor needs to be duplicated.
int my_file_descriptor = dup(log_file_descriptor);
}

输出文件示例

您可以使用输出数据文件(格式设置如下所示),在 Firebase 控制台的 Test Lab 部分显示游戏循环测试结果。显示为 /.../ 的区域可以包含您需要的任何自定义字段,只要它们与此文件中使用的其他字段的名称不冲突即可:

{
  "name": "test name",
  "start_timestamp": 0, // Timestamp of the test start (in us).
                           Can be absolute or relative
  "driver_info": "...",
  "frame_stats": [
    {
      "timestamp": 1200000, // Timestamp at which this section was written
                               It contains value regarding the period
                               start_timestamp(0) -> this timestamp (1200000 us)
      "avg_frame_time": 15320, // Average time to render a frame in ns
      "nb_swap": 52, // Number of frame rendered
      "threads": [
        {
          "name": "physics",
          "Avg_time": 8030 // Average time spent in this thread per frame in us
        },
        {
          "name": "AI",
          "Avg_time": 2030 // Average time spent in this thread per frame in us
        }
      ],
      /.../ // Any custom field you want (vertices display on the screen, nb units …)
    },
    {
      // Next frame data here, same format as above
    }
  ],
  "loading_stats": [
    {
      "name": "assets_level_1",
      "total_time": 7850, // in us
      /.../
    },
    {
      "name": "victory_screen",
      "total_time": 554, // in us
      /.../
    }

  ],
  /.../, // You can add custom fields here
}

多个游戏循环

您可能会发现,在应用中运行多个游戏循环很有用。一个循环就是由始至终完整地运行一次您的游戏应用。例如,如果您的游戏中有多个关卡,建议您分别使用一个游戏循环来启动各个关卡,而不是用一个循环来遍历所有的关卡。 这样的话,如果您的应用在第 32 关崩溃,您可以直接启动相应的游戏循环,重现崩溃问题并对 bug 修复代码进行测试。

如需让应用同时运行多个循环,请执行以下操作:

  • 使用 Test Loop Manager 运行测试时的操作方法如下:

    1. 将以下代码行添加到应用清单的 <application> 元素内:

      <meta-data
        android:name="com.google.test.loops"
        android:value="5" />

      此启动 intent 以整数参数的形式包含了目标循环。在 android:value 字段中,您可以指定一个介于 1 到 1024 之间的整数(1024 是一次测试中允许的最大循环数)。请注意,循环的索引编号从 1 开始,而不是从 0 开始。

    2. 在 Test Loop Manager 应用中,系统会显示一个选择屏幕,供您选择要运行的循环。如果您选择了多个循环,则每个循环都会在前一个循环完成后依次启动。

  • 如果您使用 Firebase 控制台运行测试,请在场景字段中输入循环编号列表或循环编号范围。

  • 如果您使用 gcloud CLI 运行测试,请使用 --scenario-numbers 标志指定循环编号列表。例如,--scenario-numbers=1,3,5 会运行循环 1、3 和 5。

  • 如果您在编写 C++ 代码并希望更改循环的行为,请将以下额外的代码传递给原生 C++ 代码:

    Kotlin

    val launchIntent = intent
    val scenario = launchIntent.getIntExtra("scenario", 0)

    Java

    Intent launchIntent = getIntent();
    int scenario = launchIntent.getIntExtra("scenario", 0);

    现在,您可以根据生成的 int 值更改循环的行为。

给游戏循环加标签

如果使用一个或多个场景标签来标记游戏循环,您和您的质量检查团队就可以轻松启动一组相关的游戏循环(例如,“所有兼容性游戏循环”),并在单个矩阵中测试这些循环。您可以创建自己的标签,也可以使用 Test Lab 提供的预定义标签:

  • com.google.test.loops.player_experience:用于重现真实用户在玩游戏时的体验的循环。使用这些循环进行测试的目标是找出真实用户在玩游戏时会遇到的问题。
  • com.google.test.loops.gpu_compatibility:用于测试 GPU 相关问题的循环。使用这些循环进行测试的目标是执行在正式环境中可能无法正常运行的 GPU 代码,以暴露硬件和驱动程序方面的问题。
  • com.google.test.loops.compatibility:用于测试各种各样的兼容性问题(包括 I/O 问题和 OpenSSL 问题)的循环。
  • com.google.test.loops.performance:用于测试设备性能的循环。例如,可以在最复杂的图形设置下运行游戏,以了解一款新设备的性能表现。

如需让您的应用运行使用相同标签的多个循环,请执行以下操作:

  • 使用 Test Loop Manager 运行测试时的操作方法如下:

    1. 在应用清单中,添加以下元数据行,并将 LABEL_NAME 替换为您选择的标签:

      <meta-data
       android:name="com.google.test.loops.LABEL_NAME"
       android:value="1,3-5" />

      android:value 字段中,您可以指定介于 1 到 1024 之间的一个范围或一组整数(1024 是一次测试中允许执行的循环次数上限),用来表示要添加标签的循环。请注意,循环的索引编号从 1 开始,而不是从 0 开始。例如,android:value="1,3-5" 会向循环 1、3、4 和 5 应用 LABEL_NAME 标签。

    2. 在 Test Loop Manager 应用的标签字段中输入一个或多个标签。

  • 如果您使用 Firebase 控制台运行测试,请在标签字段中输入一个或多个标签。

  • 如果您使用 gcloud CLI 运行测试,请使用 --scenario-labels 标志指定一个或多个场景标签(例如--scenario-labels=performance,gpu)。

应用许可支持

Test Lab 支持使用 Google Play 提供的应用许可服务的应用。在通过 Test Lab 测试您的应用时,为了成功地检查许可,您必须将该应用发布到 Play 商店中的正式版渠道。如需使用 Test Lab 测试您在 Alpha 版或 Beta 版渠道中的应用,请在将该应用上传到 Test Lab 之前取消许可检查。

已知问题

Test Lab 中的游戏循环测试存在以下已知问题:

  • 部分崩溃不支持回溯。例如,某些发布 build 可能会禁止使用 prctl(PR_SET_DUMPABLE, 0)debuggerd 进程的输出。如需了解详情,请参阅 debuggerd
  • 由于文件权限错误,因此目前不支持 API 级别 19。