游戏循环测试入门

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

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

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

您可以使用单个测试设备、一组测试设备或测试实验室运行游戏循环测试。但由于虚拟设备的图形帧速率比真机设备要低,因此不建议使用虚拟设备运行游戏循环测试。

准备工作

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

  1. 在应用清单中,为您的 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>
    

    这样一来,测试实验室就可以使用特定的 intent 触发该 activity,启动您的游戏。

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

    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
    }

    Kotlin+KTX

    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
    }

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

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

    Java

    yourActivity.finish();

    Kotlin+KTX

    yourActivity.finish()

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

创建并运行游戏循环测试

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

在本地设备上运行测试

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

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

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

在测试实验室中运行测试

您可以使用 Firebase 控制台gcloud CLI 在测试实验室中运行游戏循环测试。在开始之前,请先打开 Firebase 控制台并创建一个项目。

使用 Firebase 控制台

  1. 在 Firebase 控制台中,点击左侧的测试实验室
  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 命令行执行测试

可选功能

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

写入输出数据

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

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

Java

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

Kotlin+KTX

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

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

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);

Kotlin+KTX

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);

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 控制台的测试实验室部分显示游戏循环测试结果。 显示为 /.../ 的区域可以包含您需要的任何自定义字段,只要它们与此文件中使用的其他字段的名称不冲突即可:

{
  "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 关崩溃,您可以直接启动相应的游戏循环,重现崩溃问题并对问题修复代码进行测试。

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

  • 使用 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++ 代码:

    Java

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

    Kotlin+KTX

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

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

给游戏循环加标签

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

  • 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)。

应用许可支持

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

已知问题

测试实验室中的游戏循环测试存在以下已知问题:

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