如果您刚开始接触 Bazel,请先参阅使用 Bazel 构建 Android 教程。
图 1. 运行并行 Android 插桩测试。
借助 android_instrumentation_test
,开发者可以在 Android 模拟器和设备上测试其应用。它利用的是真实的 Android 框架 API 和 Android 测试库。
为了确保封闭性和可重现性,Bazel 会在沙盒中创建并启动 Android 模拟器,从而确保测试始终从干净的状态运行。每个测试都会获得一个隔离的模拟器实例,这样测试就可以并行运行,而无需在测试之间传递状态。
如需详细了解 Android 插桩测试,请参阅 Android 开发者文档。
请在 GitHub 问题跟踪器中提交问题。
工作原理
当您首次在 android_instrumentation_test
目标上运行 bazel test
时,Bazel 会执行以下步骤:
- 构建测试 APK、被测 APK 及其传递依赖项
- 创建、启动并缓存整洁的模拟器状态
- 启动模拟器
- 安装 APK
- 利用 Android Test Orchestrator 运行测试
- 关闭模拟器
- 报告结果
在后续的测试运行中,Bazel 会从第 2 步中创建的干净缓存状态启动模拟器,因此不会保留之前运行时留下的状态。缓存模拟器状态还有助于加快测试运行速度。
前提条件
确保您的环境满足以下前提条件:
Linux。已在 Ubuntu 16.04 和 18.04 上进行了测试。
Bazel 0.12.0 或更高版本。通过运行
bazel info release
验证版本。
bazel info release
这会生成类似于以下内容的输出:
release 4.1.0
如需验证 KVM 是否具有正确的配置,请运行以下命令:
apt-get install cpu-checker && kvm-ok
如果它输出以下消息,则表示您的配置正确无误:
INFO: /dev/kvm exists
KVM acceleration can be used
- Xvfb。如需运行无头测试(例如在 CI 服务器上),Bazel 需要 X 虚拟帧缓冲区。
如需安装该配套应用,请运行以下命令:
apt-get install xvfb
通过运行以下命令,验证 Xvfb
是否已正确安装并安装在 /usr/bin/Xvfb
中:
which Xvfb
输出如下所示:
/usr/bin/Xvfb
- 32 位库。由于测试基础架构使用的部分二进制文件是 32 位的,因此在 64 位计算机上,请确保能够运行 32 位二进制文件。对于 Ubuntu,请安装以下 32 位库:
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386
使用入门
以下是 android_instrumentation_test
的典型目标依赖项图:
图 2. android_instrumentation_test
的目标依赖关系图。
BUILD 文件
该图会转换为如下所示的 BUILD
文件:
android_instrumentation_test(
name = "my_test",
test_app = ":my_test_app",
target_device = "@android_test_support//tools/android/emulated_devices/generic_phone:android_23_x86",
)
# Test app and library
android_binary(
name = "my_test_app",
instruments = ":my_app",
manifest = "AndroidTestManifest.xml",
deps = [":my_test_lib"],
# ...
)
android_library(
name = "my_test_lib",
srcs = glob(["javatest/**/*.java"]),
deps = [
":my_app_lib",
"@maven//:androidx_test_core",
"@maven//:androidx_test_runner",
"@maven//:androidx_test_espresso_espresso_core",
],
# ...
)
# Target app and library under test
android_binary(
name = "my_app",
manifest = "AndroidManifest.xml",
deps = [":my_app_lib"],
# ...
)
android_library(
name = "my_app_lib",
srcs = glob(["java/**/*.java"]),
deps = [
"@maven//:androidx_appcompat_appcompat",
"@maven//:androidx_annotation_annotation",
]
# ...
)
规则 android_instrumentation_test
的主要属性如下:
test_app
:一个android_binary
目标。此目标包含 Espresso 和 UIAutomator 等测试代码和依赖项。所选的android_binary
目标必须指定一个指向另一个android_binary
(即被测应用)的instruments
属性。target_device
:一个android_device
目标。此目标说明了 Bazel 用于创建、启动和运行测试的 Android 模拟器的规范。如需了解详情,请参阅选择 Android 设备的部分。
测试应用的 AndroidManifest.xml
必须包含 <instrumentation>
标记。
此标记必须指定目标应用的软件包的属性以及插桩测试运行程序的完全限定类名称 androidx.test.runner.AndroidJUnitRunner
。
下面是测试应用的 AndroidTestManifest.xml
示例:
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.android.app.test"
android:versionCode="1"
android:versionName="1.0">
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.example.android.app" />
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="27" />
<application >
<!-- ... -->
</application>
</manifest>
WORKSPACE 依赖项
如需使用此规则,您的项目需要依赖于以下外部代码库:
@androidsdk
:Android SDK。通过 Android Studio 下载此文件。@android_test_support
:托管测试运行程序、模拟器启动器和android_device
目标。您可以点击此处找到最新版本。
将以下代码行添加到 WORKSPACE
文件中,以启用这些依赖项:
# Android SDK
android_sdk_repository(
name = "androidsdk",
path = "/path/to/sdk", # or set ANDROID_HOME
)
# Android Test Support
ATS_COMMIT = "$COMMIT_HASH"
http_archive(
name = "android_test_support",
strip_prefix = "android-test-%s" % ATS_COMMIT,
urls = ["https://github.com/android/android-test/archive/%s.tar.gz" % ATS_COMMIT],
)
load("@android_test_support//:repo.bzl", "android_test_repositories")
android_test_repositories()
Maven 依赖项
如需管理对代码库(例如 Google Maven 或 Maven Central)中 Maven 工件的依赖项,您应使用 Maven 解析器,例如 rules_jvm_external
。
本页面的其余部分介绍了如何使用 rules_jvm_external
从 Maven 代码库中解析和提取依赖项。
选择 android_device 目标
android_instrumentation_test.target_device
指定要在哪部 Android 设备上运行测试。这些 android_device
目标在 @android_test_support
中定义。
例如,您可以通过运行以下命令来查询特定目标的源代码:
bazel query --output=build @android_test_support//tools/android/emulated_devices/generic_phone:android_23_x86
这会生成类似于以下内容的输出:
# .../external/android_test_support/tools/android/emulated_devices/generic_phone/BUILD:43:1
android_device(
name = "android_23_x86",
visibility = ["//visibility:public"],
tags = ["requires-kvm"],
generator_name = "generic_phone",
generator_function = "make_device",
generator_location = "tools/android/emulated_devices/generic_phone/BUILD:43",
vertical_resolution = 800,
horizontal_resolution = 480,
ram = 2048,
screen_density = 240,
cache = 32,
vm_heap = 256,
system_image = "@android_test_support//tools/android/emulated_devices/generic_phone:android_23_x86_images",
default_properties = "@android_test_support//tools/android/emulated_devices/generic_phone:_android_23_x86_props",
)
设备目标名称使用以下模板:
@android_test_support//tools/android/emulated_devices/device_type:system_api_level_x86_qemu2
如需启动 android_device
,必须具有所选 API 级别的 system_image
。如需下载系统映像,请使用 Android SDK 的 tools/bin/sdkmanager
。例如,如需下载 generic_phone:android_23_x86
的系统映像,请运行 $sdk/tools/bin/sdkmanager
"system-images;android-23;default;x86"
。
如需查看 @android_test_support
中受支持的 android_device
目标的完整列表,请运行以下命令:
bazel query 'filter("x86_qemu2$", kind(android_device, @android_test_support//tools/android/emulated_devices/...:*))'
Bazel 目前仅支持基于 x86 的模拟器。为了获得更好的性能,请使用 QEMU2
android_device
目标,而不是 QEMU
目标。
运行测试
如需运行测试,请将以下代码行添加到项目的 project root:/.bazelrc
文件中。
# Configurations for testing with Bazel
# Select a configuration by running
# `bazel test //my:target --config={headless, gui, local_device}`
# Headless instrumentation tests (No GUI)
test:headless --test_arg=--enable_display=false
# Graphical instrumentation tests. Ensure that $DISPLAY is set.
test:gui --test_env=DISPLAY
test:gui --test_arg=--enable_display=true
# Testing with a local emulator or device. Ensure that `adb devices` lists the
# device.
# Run tests serially.
test:local_device --test_strategy=exclusive
# Use the local device broker type, as opposed to WRAPPED_EMULATOR.
test:local_device --test_arg=--device_broker_type=LOCAL_ADB_SERVER
# Uncomment and set $device_id if there is more than one connected device.
# test:local_device --test_arg=--device_serial_number=$device_id
然后,使用其中一种配置运行测试:
bazel test //my/test:target --config=gui
bazel test //my/test:target --config=headless
bazel test //my/test:target --config=local_device
请仅使用一个配置,否则测试将失败。
无头测试
借助 Xvfb
,您可以使用不带图形界面的模拟器进行测试,这也称为无头测试。如需在运行测试时停用图形界面,请将测试参数 --enable_display=false
传递给 Bazel:
bazel test //my/test:target --test_arg=--enable_display=false
GUI 测试
如果设置了 $DISPLAY
环境变量,则可以在测试运行时启用模拟器的图形界面。为此,请将以下测试参数传递给 Bazel:
bazel test //my/test:target --test_arg=--enable_display=true --test_env=DISPLAY
使用本地模拟器或设备进行测试
Bazel 还支持直接在本地启动的模拟器或已连接的设备上进行测试。传递 --test_strategy=exclusive
和 --test_arg=--device_broker_type=LOCAL_ADB_SERVER
标志以启用本地测试模式。如果有多个已连接的设备,请传递标志 --test_arg=--device_serial_number=$device_id
,其中 $device_id
是 adb devices
中列出的设备/模拟器的 ID。
示例项目
如果您正在寻找规范的项目示例,请参阅适用于使用 Espresso 和 UIAutomator 的项目的 Android 测试示例。
Espresso 设置
如果您使用 Espresso (androidx.test.espresso
) 编写界面测试,则可以使用以下代码段在您的 Bazel 工作区中设置常用 Espresso 工件及其依赖项的列表:
androidx.test.espresso:espresso-core
androidx.test:rules
androidx.test:runner
javax.inject:javax.inject
org.hamcrest:java-hamcrest
junit:junit
整理这些依赖项的一种方法是在 project root/BUILD.bazel
文件中创建 //:test_deps
共享库:
java_library(
name = "test_deps",
visibility = ["//visibility:public"],
exports = [
"@maven//:androidx_test_espresso_espresso_core",
"@maven//:androidx_test_rules",
"@maven//:androidx_test_runner",
"@maven//:javax_inject_javax_inject"
"@maven//:org_hamcrest_java_hamcrest",
"@maven//:junit_junit",
],
)
然后,在 project root/WORKSPACE
中添加所需的依赖项:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "2.8"
RULES_JVM_EXTERNAL_SHA = "79c9850690d7614ecdb72d68394f994fef7534b292c4867ce5e7dec0aa7bdfad"
http_archive(
name = "rules_jvm_external",
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
sha256 = RULES_JVM_EXTERNAL_SHA,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"junit:junit:4.12",
"javax.inject:javax.inject:1",
"org.hamcrest:java-hamcrest:2.0.0.0"
"androidx.test.espresso:espresso-core:3.1.1",
"androidx.test:rules:aar:1.1.1",
"androidx.test:runner:aar:1.1.1",
],
repositories = [
"https://maven.google.com",
"https://repo1.maven.org/maven2",
],
)
最后,在测试 android_binary
目标中,添加 //:test_deps
依赖项:
android_binary(
name = "my_test_app",
instruments = "//path/to:app",
deps = [
"//:test_deps",
# ...
],
# ...
)
提示
读取测试日志
使用 --test_output=errors
可输出失败测试的日志,使用 --test_output=all
可输出所有测试输出。如果您要查找单个测试日志,请前往 $PROJECT_ROOT/bazel-testlogs/path/to/InstrumentationTestTargetName
。
例如,BasicSample
规范项目的测试日志位于 bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest
中,请运行以下命令:
tree bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest
这会产生以下输出:
$ tree bazel-testlogs/ui/espresso/BasicSample/BasicSampleInstrumentationTest
.
├── adb.409923.log
├── broker_logs
│ ├── aapt_binary.10.ok.txt
│ ├── aapt_binary.11.ok.txt
│ ├── adb.12.ok.txt
│ ├── adb.13.ok.txt
│ ├── adb.14.ok.txt
│ ├── adb.15.fail.txt
│ ├── adb.16.ok.txt
│ ├── adb.17.fail.txt
│ ├── adb.18.ok.txt
│ ├── adb.19.fail.txt
│ ├── adb.20.ok.txt
│ ├── adb.21.ok.txt
│ ├── adb.22.ok.txt
│ ├── adb.23.ok.txt
│ ├── adb.24.fail.txt
│ ├── adb.25.ok.txt
│ ├── adb.26.fail.txt
│ ├── adb.27.ok.txt
│ ├── adb.28.fail.txt
│ ├── adb.29.ok.txt
│ ├── adb.2.ok.txt
│ ├── adb.30.ok.txt
│ ├── adb.3.ok.txt
│ ├── adb.4.ok.txt
│ ├── adb.5.ok.txt
│ ├── adb.6.ok.txt
│ ├── adb.7.ok.txt
│ ├── adb.8.ok.txt
│ ├── adb.9.ok.txt
│ ├── android_23_x86.1.ok.txt
│ └── exec-1
│ ├── adb-2.txt
│ ├── emulator-2.txt
│ └── mksdcard-1.txt
├── device_logcat
│ └── logcat1635880625641751077.txt
├── emulator_itCqtc.log
├── outputs.zip
├── pipe.log.txt
├── telnet_pipe.log.txt
└── tmpuRh4cy
├── watchdog.err
└── watchdog.out
4 directories, 41 files
读取模拟器日志
android_device
目标的模拟器日志存储在名称为 emulator_xxxxx.log
的 /tmp/
目录中,其中 xxxxx
是随机生成的字符序列。
使用以下命令查找最新的模拟器日志:
ls -1t /tmp/emulator_*.log | head -n 1
针对多个 API 级别进行测试
如果您想针对多个 API 级别进行测试,可以使用列表推理为每个 API 级别创建测试目标。例如:
API_LEVELS = [
"19",
"20",
"21",
"22",
]
[android_instrumentation_test(
name = "my_test_%s" % API_LEVEL,
test_app = ":my_test_app",
target_device = "@android_test_support//tools/android/emulated_devices/generic_phone:android_%s_x86_qemu2" % API_LEVEL,
) for API_LEVEL in API_LEVELS]
已知问题
- 分叉的 adb 服务器进程在测试后不会终止
- 虽然 APK 构建适用于所有平台(Linux、macOS、Windows),但测试仅适用于 Linux。
- 即使使用
--config=local_adb
,用户仍然需要指定android_instrumentation_test.target_device
。 - 如果使用本地设备或模拟器,Bazel 不会在测试后卸载 APK。运行以下命令清理软件包:
adb shell pm list
packages com.example.android.testing | cut -d ':' -f 2 | tr -d '\r' | xargs
-L1 -t adb uninstall