使用 Gradle 管理的设备扩展测试

Gradle 管理的设备可以提高自动化插桩测试的一致性、性能和可靠性。此功能适用于 API 级别 27 及更高级别,可让您在项目的 Gradle 文件中配置虚拟或远程实体测试设备。在执行自动化测试时,构建系统会使用这些配置来完全代管(即创建、部署和拆解)这些设备。

借助这项功能,Gradle 不仅对正在运行的测试了如指掌,而且设备的生命周期也尽在把握,因此可从以下几个方面提高测试体验的质量:

  • 处理设备相关问题,以确保顺利执行测试
  • 对于虚拟设备,使用模拟器快照缩短设备启动时间,减少内存用量,并在每次测试完毕后都让设备恢复到干净状态,方便进行下一次测试
  • 缓存测试结果,并且仅重新运行可能会提供不同结果的测试
  • 为本地测试和远程测试提供一致的测试运行环境

创建 Gradle 管理的虚拟设备

您可以在模块级 build 文件中指定希望 Gradle 用于测试应用的虚拟设备。以下代码示例将创建一个搭载 API 级别 30 的 Pixel 2 作为 Gradle 管理的设备。

Kotlin

android {
  testOptions {
    managedDevices {
      localDevices {
        create("pixel2api30") {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // Use only API levels 27 and higher.
          apiLevel = 30
          // To include Google services, use "google".
          systemImageSource = "aosp"
        }
      }
    }
  }
}

Groovy

android {
  testOptions {
    managedDevices {
      localDevices {
        pixel2api30 {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // Use only API levels 27 and higher.
          apiLevel = 30
          // To include Google services, use "google".
          systemImageSource = "aosp"
        }
      }
    }
  }
}

定义设备组

为了帮助您跨多个设备配置(例如不同的 API 级别和设备规格)扩展测试,您可以定义多个由 Gradle 管理的设备并将其添加到已命名的组。然后,Gradle 可在组中的所有设备上并行执行测试。

下面的示例显示了如何将两个设备添加到名为 phoneAndTablet 的设备组。

Kotlin

testOptions {
  managedDevices {
    localDevices {
      create("pixel2api29") { ... }
      create("nexus9api30") { ... }
    }
    groups {
      create("phoneAndTablet") {
        targetDevices.add(devices["pixel2api29"])
        targetDevices.add(devices["nexus9api30"])
      }
    }
  }
}

Groovy

testOptions {
  managedDevices {
    localDevices {
      pixel2api29 { ... }
      nexus9api30 { ... }
    }
    groups {
      phoneAndTablet {
        targetDevices.add(devices.pixel2api29)
        targetDevices.add(devices.nexus9api30)
      }
    }
  }
}

运行测试

如需使用您配置的由 Gradle 管理的设备运行测试,请使用以下命令。device-name 是您在 Gradle 构建脚本(例如 pixel2api30)中配置的设备名称,BuildVariant 是要测试的应用 build 变体。

在 Windows 上:

gradlew device-nameBuildVariantAndroidTest

在 Linux 或 macOS 上:

./gradlew device-nameBuildVariantAndroidTest

如需在 Gradle 管理的一设备上运行您的测试,请使用以下命令。

在 Windows 上:

gradlew group-nameGroupBuildVariantAndroidTest

在 Linux 或 macOS 上:

./gradlew group-nameGroupBuildVariantAndroidTest

测试输出包括指向包含测试报告的 HTML 文件的路径。您还可以将测试结果导入 Android Studio 以供进一步分析,方法是在该 IDE 中依次点击 Run > Test History

启用测试分片

Gradle 管理的设备支持测试分片,可以让您在多个并行运行的相同虚拟设备实例(称为“分片”)间拆分测试套件。使用测试分片有助于缩短总测试执行时间,但代价是会消耗额外的计算资源。

如需设置要在指定测试运行中使用的分片数,请在 gradle.properties 文件中进行以下设置:

android.experimental.androidTest.numManagedDeviceShards=<number_of_shards>

使用此选项运行测试时,Gradle 管理的设备会为测试运行中的每个设备配置文件配置您指定的分片数。例如,如果您将测试部署到包含 3 个设备的设备组,并将 numManagedDeviceShards 设为 2,那么 Gradle 管理的设备将为测试运行配置共计 6 个虚拟设备。

测试完成后,对于测试运行中使用的每个分片,Gradle 会将相应测试结果分别输出到一个 .proto 文件中。

使用自动化测试设备

Gradle 管理的设备支持一种称为自动化测试设备 (ATD) 的新型模拟器设备,该设备经过优化,可以减少运行插桩测试时所需的 CPU 和内存资源。ATD 通过以下几种方式提高运行时性能:

  • 移除通常对应用测试无用的预安装应用
  • 停用通常对应用测试无用的某些后台服务
  • 停用硬件渲染

首先,请务必将 Android 模拟器更新到最新可用版本。然后,在模块级 build 文件中定义 Gradle 管理的设备时指定“-atd”映像,如下所示:

Kotlin

android {
  testOptions {
    managedDevices {
      localDevices {
        create("pixel2api30") {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // ATDs currently support only API level 30.
          apiLevel = 30
          // You can also specify "google-atd" if you require Google Play Services.
          systemImageSource = "aosp-atd"
        }
      }
    }
  }
}

Groovy

android {
  testOptions {
    managedDevices {
      localDevices {
        pixel2api30 {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // ATDs currently support only API level 30.
          apiLevel = 30
          // You can also specify "google-atd" if you require Google Play Services.
          systemImageSource = "aosp-atd"
        }
      }
    }
  }
}

您也可以创建设备组,就像对其他由 Gradle 管理的设备那样。若要进一步利用性能改进,您还可以将 ATD 与测试分片结合使用,以缩短测试套件的总测试执行时间。

已从 ATD 映像中移除的组件

除了在无头模式下运行之外,ATD 还通过移除或停用应用代码测试通常不需要的应用和服务来优化性能。下表简要介绍了我们在 ATD 映像中已移除或停用的组件,并说明了这些组件可能无用的原因。

ATD 映像中已移除的组件 运行自动化测试时可能不需要此组件的原因
Google 产品应用:
  • 邮件
  • 地图
  • Chrome
  • 信息
  • Play 商店等
自动化测试应当侧重于您自己的应用的逻辑,同时假定其他应用或平台能够正常运行。

借助 Espresso-Intents,您可以匹配和验证您的传出 intent,甚至可以提供桩响应来代替实际的 intent 响应。

设置方面的应用和服务:
  • CarrierConfig
  • EmergencyInfo
  • OneTimeInitializer
  • PhotoTable(屏保)
  • 配置
  • “设置”应用
  • StorageManager
  • 电话 APN 配置
  • WallpaperCropper
  • WallpaperPicker
这些应用为最终用户提供用于更改平台设置、设置设备或管理设备存储空间所需的 GUI。这通常不在应用级自动化测试的范围之内。


注意:ATD 映像中仍然提供了设置提供程序

SystemUI 自动化测试应当侧重于您自己的应用的逻辑,同时假定其他应用或平台能够正常运行。
AOSP 应用和服务:
  • Browser2
  • 日历
  • Camera2
  • 通讯录
  • 拨号器
  • 桌面时钟
  • Gallery2
  • LatinIME
  • Launcher3QuickStep
  • 音乐
  • QuickSearchBox
  • SettingsIntelligence
这些应用和服务通常不在应用代码的自动化测试范围之内。

使用 Firebase Test Lab 设备

使用 Gradle 管理的设备时,您可以在 Firebase Test Lab 设备上大规模运行自动化插桩测试。利用 Test Lab,您可以在各种 Android 设备(包括实体设备和虚拟设备)上同时运行测试。这些测试在远程 Google 数据中心运行。得益于 Gradle 管理的设备 (GMD) 的支持,构建系统可以根据您的配置全面管理针对这些 Test Lab 设备运行的测试。

开始使用

以下步骤介绍了如何将 Firebase Test Lab 设备与 Gradle 管理的设备搭配使用。请注意,这些步骤使用 gcloud CLI 提供用户凭据,这些凭据可能不适用于所有开发环境。如需详细了解应根据您的需求使用哪种身份验证流程,请参阅应用默认凭据的工作原理

  1. 如需创建 Firebase 项目,请前往 Firebase 控制台。点击添加项目,然后按照屏幕上的提示创建项目。记住项目 ID。

  2. 如需安装 Google Cloud CLI,请按照安装 gcloud CLI 中的步骤操作。

  3. 配置本地环境。

    1. 在 gcloud 中关联到您的 Firebase 项目:

      gcloud config set project FIREBASE_PROJECT_ID
      
    2. 授权使用您的用户凭据以访问 API。我们建议您在模块级 build 脚本中使用 DSL服务账号 JSON 文件传递给 Gradle 以进行授权:

      Kotlin

      firebaseTestLab {
        ...
        serviceAccountCredentials.set(file(SERVICE_ACCOUNT_JSON_FILE))
      }
      

      Groovy

      firebaseTestLab {
        ...
        serviceAccountCredentials = file(SERVICE_ACCOUNT_JSON_FILE)
      }
      

      或者,您也可以使用以下终端命令手动授权:

      gcloud auth application-default login
      
    3. 可选:将您的 Firebase 项目添加为配额项目。仅在您超出了 Test Lab 的免费配额时,才需要执行此步骤。

      gcloud auth application-default set-quota-project FIREBASE_PROJECT_ID
      
  4. 启用必需的 API。

    “Google 开发者控制台 API 库”页面中,启用 Cloud Testing APICloud Tool Results API,方法是在控制台顶部的搜索框中输入这些 API 名称,然后点击每个 API 的概览页面中的启用 API

  5. 配置 Android 项目。

    1. 在顶级构建脚本中添加 Firebase Test Lab 插件:

      Kotlin

      plugins {
        ...
        id("com.google.firebase.testlab") version "0.0.1-alpha05" apply false
      }
      

      Groovy

      plugins {
        ...
        id 'com.google.firebase.testlab' version '0.0.1-alpha05' apply false
      }
      
    2. gradle.properties 文件中启用自定义设备类型:

      android.experimental.testOptions.managedDevices.customDevice=true
      
    3. 在模块级构建脚本中添加 Firebase Test Lab 插件:

      Kotlin

      plugins {
       ...
       id "com.google.firebase.testlab"
      }
      

      Groovy

      plugins {
       ...
       id 'com.google.firebase.testlab'
      }
      

指定 Test Lab 设备

您可以在模块级构建脚本中指定适用于 Gradle 的 Firebase Test Lab 设备,用于测试应用。以下代码示例将创建一个搭载 API 级别 30 的 Pixel 3,作为 Gradle 管理的 Test Lab 设备(称为 ftlDevice)。当您将 com.google.firebase.testlab 插件应用于模块时,firebaseTestLab {} 代码块将可用。

Kotlin

firebaseTestLab {
  managedDevices {
    create("ftlDevice") {
      device = "Pixel3"
      apiLevel = 30
    }
  }
  ...
}

Groovy

firebaseTestLab {
  managedDevices {
    ftlDevice {
      device = "Pixel3"
      apiLevel = 30
    }
  }
  ...
}

如需定义一组 Gradle 管理的设备(包括 Firebase Test Lab 设备),请参阅定义设备组

如需运行测试,请使用用于运行其他 Gradle 管理设备的同一命令。请注意,Gradle 不会并行运行测试,也不会支持 Test Lab 设备的其他 Google Cloud CLI 配置。

利用智能分片优化测试运行

在 Gradle 管理的 Test Lab 设备上进行测试支持智能分片。智能分片可自动将测试分布到多个分片,使每个分片的运行时间大致相同,从而减少手动分配工作量并缩短总体测试运行时长。智能分片会使用您的测试历史记录或与之前运行测试所花时间有关的信息,以最佳方式分发测试。请注意,您需要安装适用于 Firebase Test Lab 的 Gradle 插件 0.0.1-alpha05 版本,才能使用智能分片。

如需启用智能分片,请指定每个分片中测试应花费的时间。您应将目标分片时长设置为至少比 timeoutMinutes 少 5 分钟,以避免分片在测试完成之前被取消的情况。

firebaseTestLab {
  ...
  testOptions {
    targetedShardDurationMinutes = 2
  }
}

如需了解详情,请参阅 Firebase Test Lab 设备 DSL 选项

更新了 Test Lab 设备的 DSL

您可以配置更多 DSL 选项,以帮助自定义测试运行,或者从您可能已经在使用的其他解决方案进行迁移。请参阅以下代码段中介绍的其中一些选项。

firebaseTestLab {
  ...

  /**
   * A path to a JSON file that contains service account credentials to access to
   * a Firebase Test Lab project.
   */
  serviceAccountCredentials.set(file("your_service_account_credentials.json"))


  testOptions {
    fixture {
      /**
       * Whether to grant permissions on the device before tests begin.
       * Available options are "all" or "none".
       *
       * Default value is "all".
       */
      grantedPermissions = "all"

      /**
       * Map of files to push to the device before starting the test.
       *
       * The key is the location on the device.
       * The value is the location of the file, either local or in Google Cloud.
       */
      extraDeviceFiles["/sdcard/dir1/file1.txt"] = "local/file.txt"
      extraDeviceFiles["/sdcard/dir2/file2.txt"] = "gs://bucket/file.jpg"

      /**
       * The name of the network traffic profile.
       *
       * Specifies network conditions to emulate when running tests.
       *
       * Default value is empty.
       */
      networkProfile = "LTE"
    }

    execution {
      /**
       * The maximum time to run the test execution before cancellation,
       * measured in minutes. Does not include the setup or teardown of device,
       * and is handled server-side.
       *
       * The maximum possible testing time is 45 minutes on physical devices
       * and 60 minutes on virtual devices.
       *
       * Defaults to 15 minutes.
       */
       timeoutMinutes = 30

      /**
       * Number of times the test should be rerun if tests fail.
       * The number of times a test execution should be retried if one
       * or more of its test cases fail.
       *
       * The max number of times is 10.
       *
       * The default number of times is 0.
       */
      maxTestReruns = 2

      /**
       * Ensures only a single attempt is made for each execution if
       * an infrastructure issue occurs. This doesn't affect `maxTestReruns`.
       * Normally, two or more attempts are made by Firebase Test Lab if a
       * potential infrastructure issue is detected. This is best enabled for
       * latency sensitive workloads. The number of execution failures might be
       * significantly greater with `failFast` enabled.
       *
       * Defaults to false.
       */
      failFast = false

      /**
       * The number of shards to split the tests across.
       *
       * Default to 0 for no sharding.
       */
      numUniformShards = 20
    }

    /**
     * For smart sharding, the target length of time each shard should takes in
     * minutes. Maxes out at 50 shards for physical devices and 100 shards for
     * virtual devices.
     *
     * Only one of numUniformShards or targetedShardDurationMinutes can be set.
     *
     * Defaults to 0 for no smart sharding.
     */
     targetedShardDurationMinutes = 15
    }

    results {
      /**
       * The name of the Google storage bucket to store the test results in.
       *
       * If left unspecified, the default bucket is used.
       *
       * Please refer to Firebase Test Lab permissions for required permissions
       * for using the bucket.
       */
      cloudStorageBucket = "bucketLocationName"

      /**
       * Name of test results for the Firebase console history list.
       * All tests results with the same history name are grouped
       * together in the Firebase console in a time-ordered test history list.
       *
       * Defaults to the application label in the APK manifest in Flank/Fladle.
       */
      resultsHistoryName = "application-history"

      /**
       * List of paths to copy from the test device's storage to the test
       * results folder. These must be absolute paths under /sdcard or
       * /data/local/tmp.
       */
      directoriesToPull.addAll(
        "/sdcard/path/to/something"
      )

      /**
       * Whether to enable video recording during the test.
       *
       * Disabled by default.
       */
      recordVideo = false

      /**
       * Whether to enable performance metrics. If enabled, monitors and records
       * performance metrics such as CPU, memory, and network usage.
       *
       * Defaults to false.
       */
      performanceMetrics = true
  }
}