当用户设置新设备或重新安装您的应用时,Android 可以通过云端备份或设备间传输来恢复应用的数据。如果您的备份规则遗漏了重要数据,或者您的应用无法使用其恢复的数据,用户会丢失其设置和进度,而不会看到错误。
自动化备份和恢复测试可帮助您在发布应用之前发现这些问题。这些测试是 JUnit 5 测试,在开发机器上运行,并通过 Android 调试桥 (adb) 控制已连接的设备。每项测试都会在设备上植入应用数据,通过 Android 备份框架备份和恢复应用,然后检查数据是否完好无损。您可以在模块的 build 文件中将测试配置为 Android Gradle 插件 (AGP) 测试套件。
运作方式
备份和恢复测试套件包含两个协同工作的部分:
- 主机测试是在开发机器上运行的 JUnit 5 测试。它们会为每台设备使用一个
BackupRestoreController来播种数据、备份和恢复应用,以及检查结果。 - 设备操作是实现
BackupDeviceAction的类。它们在设备上应用的进程中运行,因此可以通过应用自己的代码读取和写入数据。主机测试按类名称调用设备操作。
当您运行该套件时,AGP 会构建您的应用以及包含设备操作的测试 APK。测试框架会在测试使用的每台设备上安装这两个 APK,并在每次测试之前清除应用的数据。
内置存储类型
下表列出了 StorageDomain 子类,这些子类可让您在不编写设备操作的情况下播种和验证数据:
| 存储网域 | 数据 |
|---|---|
StorageDomain.Preference |
SharedPreferences 文件中的密钥。 |
StorageDomain.DataStorePreference |
Preferences DataStore 文件中的键。 |
StorageDomain.Database |
SQLite 数据库表中的一行。 |
StorageDomain.TextFile |
应用内部存储空间中的文本文件。 |
StorageDomain.BinaryFile |
应用内部存储空间中的二进制文件。 |
StorageDomain.ExcludedData |
另一个不应恢复其数据的内置存储网域,例如备份规则排除的数据。 |
如需测试其他数据(例如 Proto DataStore 文件或 Room 数据库),请编写设备操作。
交通方式
每个备份都使用 BackupTransportMode 值。下表列出了支持的传输模式:
| 模式 | 场景 |
|---|---|
LOCAL |
备份到设备的本地备份传输。备份数据会保留在设备上。 |
CLOUD_ENCRYPTED |
云端备份,例如从启用了客户端加密功能的设备保存到用户 Google 账号中的备份。 |
CLOUD_UNENCRYPTED |
来自未启用客户端加密功能的设备的云端备份。 |
DEVICE_TO_DEVICE |
设备到设备传输,例如通过数据线或 Wi-Fi。 |
LOCAL 适用于任何受支持的设备。云端和设备到设备模式创建的备份与 Android Studio 中的备份应用数据操作相同:备份会保存到开发机器,而不是上传或发送到其他设备。云端和设备到设备模式要求设备上安装有 Google Play 服务,并且应用具有可调试 build。如果您的备份规则依赖于客户端加密,请测试这两种云端模式。
要求
如需使用自动备份和恢复测试,请确保您的环境满足以下要求:
- Gradle 9.6.0 版或更高版本。
- JDK 17 或更高版本。
- 通过 adb 连接的搭载 Android 12(API 级别 31)或更高版本的设备或模拟器:
- 搭载 API 级别 36.1 或更高级别的设备或模拟器需要 Google Play 服务 26.33.30 或更高版本。
- 如需测试两台设备之间的传输,请连接两台设备。
兼容性
下表列出了自动备份和恢复测试的最低版本和推荐版本:
| 组件 | 最低版本 | 推荐的版本 |
|---|---|---|
| Android Studio | Rabbit 2 Canary 1 | Rabbit 2 Canary 4 或更高版本 |
| Android Gradle 插件 (AGP) | 9.5.0-alpha06 | 9.5.0-alpha09 或更高版本 |
androidx.test.backup 个库 |
1.0.0-alpha01 | 1.0.0-alpha04 或更高版本 |
每个预览版 AGP 都需要匹配的预览版 Android Studio。如需了解详情,请参阅 AGP 预览版。
设置项目
如需向应用模块添加备份和恢复测试套件,请完成以下步骤:
启用实验性标志
在项目的 gradle.properties 文件中,启用测试套件和备份测试:
android.experimental.testSuiteSupport=true
android.experimental.enableBackupTest=true
声明测试套件
在应用模块的 build.gradle.kts 文件中,在 testOptions 代码块中声明备份测试套件:
android {
testOptions {
backupTests {
create("backupTest") {
backupTestLibraryVersion = "1.0.0-alpha04"
}
}
}
}
套件名称(在本例中为 backupTest)决定了源代码集目录和 Gradle 任务名称。您可以使用以下可选属性自定义套件:
- 使用
backupTestLibraryVersion设置 AGP 添加到套件中的androidx.test.backup库的版本,例如"1.0.0-alpha04"。此为可选属性。如果您省略此属性,AGP 会根据 AGP 版本回退到默认库版本。本页中的示例需要使用 1.0.0-alpha04 或更高版本。 - 使用
targetVariants指定套件测试的 build 变体,例如targetVariants.add("demoDebug")。此为可选属性。如果您省略此参数,该套件会测试模块的testBuildType的变体,默认情况下为debug。我们建议测试可调试变体,因为云端和设备到设备传输模式需要可调试应用。
AGP 会将主机库 androidx.test.backup:backup-host 和 JUnit 5 添加到主机测试中。它会将设备库 androidx.test.backup:backup 添加到测试 APK,并设置测试 APK 的插桩测试运行程序。
如需添加其他依赖项,请使用套件的 hostJar 和 testApk 代码块。
例如,如需在主机测试中使用 Truth,请将其添加到 hostJar 代码块中:
create("backupTest") {
hostJar {
dependencies {
implementation("com.google.truth:truth:1.4.2")
}
}
}
创建源代码集
创建与套件名称匹配的源代码集目录:
- 将主机测试放在
src/{suiteName}/test/kotlin/中。 - 将设备操作放在
src/{suiteName}/androidTest/kotlin/中。
例如,名为 backupTest 的套件使用以下目录结构:
app/src/backupTest/
├── androidTest/
│ └── kotlin/com/example/myapp/ThemeActions.kt
└── test/
└── kotlin/com/example/myapp/SettingsBackupTest.kt
检查备份配置
测试会备份和恢复应用的备份配置中包含的数据。确保已为您的应用启用备份,并且您的备份规则包含您要测试的数据。
编写备份和恢复测试
在套件的 test 源代码集中编写主机测试。为每个测试类添加 @ExtendWith(BackupRestoreExtension::class) 注解,以便为测试提供已连接的设备。测试会针对套件所针对的应用变体运行,因此您无需设置其应用 ID。
每个测试方法都会为其使用的每台设备声明一个 BackupRestoreController 参数。
测试内置存储类型
如需测试内置存储类型,请使用要植入的数据、备份文件的目录和传输模式调用 runBackupRestoreFlow。该方法会在设备上运行以下步骤:
- 为每个存储网域提供种子。
- 停止应用。
- 备份应用。
- 清除应用的数据。
- 恢复备份。
- 验证每个存储网域是否都具有种子值,或者在封装在
StorageDomain.ExcludedData中时是否不存在。
如果某个步骤失败,该方法会抛出异常,导致测试失败。以下示例使用本地备份传输测试 SharedPreferences、Preferences DataStore、SQLite、内部文件和排除文件:
package com.example.myapp
import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import androidx.test.backup.host.StorageDomain
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir
@ExtendWith(BackupRestoreExtension::class)
class SettingsBackupTest {
@Test
fun settingsSurviveBackupAndRestore(
device: BackupRestoreController,
@TempDir outputDir: Path,
): Unit = runBlocking {
device.runBackupRestoreFlow(
storages = listOf(
StorageDomain.Preference(
prefName = "settings",
key = "channels",
value = setOf("news", "alerts"),
),
StorageDomain.DataStorePreference(
dataStoreName = "user_prefs",
key = "font_scale",
value = 1.25f,
),
StorageDomain.Database(
dbName = "notes.db",
table = "notes",
primaryKeyCol = "id",
primaryKeyVal = 1,
columnValues = mapOf("title" to "Groceries"),
),
StorageDomain.TextFile(
path = "drafts/note.txt",
content = "Buy milk",
),
// Check that a file excluded by backup rules isn't restored.
StorageDomain.ExcludedData(
StorageDomain.TextFile(
path = "auth/session.txt",
content = "temp-token",
)
),
),
outputDir = outputDir,
mode = BackupTransportMode.LOCAL,
)
}
}
定义存储网域时,请注意以下几点:
Preference采用不带.xml扩展名的SharedPreferences文件的名称。DataStorePreference采用传递给preferencesDataStore的名称。为value使用与应用的偏好设置键相同的 Kotlin 类型,例如为floatPreferencesKey使用1.25f,以便应用可以读取恢复的条目。TextFile和BinaryFile会根据应用的filesDir解析相对路径。Database使用 SQL 写入行,如果表不存在,则使用TEXT列创建表。如果您的应用使用 Room,请使用采用 DAO 的设备操作测试数据库内容,以便数据库与 Room 的架构保持一致。ExcludedData要求在同一runBackupRestoreFlow调用中至少有一个未排除的存储网域。- 当您设置
isDeviceProtectedStorage = true时,Preference、DataStorePreference、Database、TextFile和BinaryFile会以设备保护存储空间(而非应用的默认存储空间)为目标。
写入设备操作
如需植入或验证内置存储类型未涵盖的数据,请在套件的 androidTest 源代码集中编写设备操作。实现 BackupDeviceAction 并替换 execute,以便使用应用的 Context 和主机测试中的参数运行操作。返回使用 BackupDeviceActionResult.success 或 BackupDeviceActionResult.failure 创建的 BackupDeviceActionResult,并将错误消息传递给 failure。
execute 方法在应用的进程中在后台线程上运行,因此它可以阻塞,直到读写操作完成。如需访问测试运行程序的插桩(例如 UiAutomation),请调用 InstrumentationRegistry.getInstrumentation。
以下示例在 Proto DataStore 中播种并验证主题设置,其中 settingsDataStore 是应用中定义的 DataStore<Settings> 属性:
// In app/src/backupTest/androidTest/kotlin/com/example/myapp/ThemeActions.kt
package com.example.myapp
import android.content.Context
import androidx.test.backup.BackupDeviceAction
import androidx.test.backup.BackupDeviceActionArgs
import androidx.test.backup.BackupDeviceActionResult
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
class SeedThemeAction : BackupDeviceAction {
override fun execute(
context: Context,
args: BackupDeviceActionArgs,
): BackupDeviceActionResult {
val theme = args["theme"]
?: return BackupDeviceActionResult.failure("Missing theme argument")
runBlocking {
context.settingsDataStore.updateData {
it.toBuilder().setTheme(theme).build()
}
}
return BackupDeviceActionResult.success()
}
}
class VerifyThemeAction : BackupDeviceAction {
override fun execute(
context: Context,
args: BackupDeviceActionArgs,
): BackupDeviceActionResult {
val expected = args["theme"]
val actual = runBlocking {
context.settingsDataStore.data.first().theme
}
return if (actual == expected) {
BackupDeviceActionResult.success()
} else {
BackupDeviceActionResult.failure("Expected $expected, found $actual")
}
}
}
如需在 runBackupRestoreFlow 中运行填充和验证操作,请将其类名称传递给 StorageDomain.CustomData:
package com.example.myapp
import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import androidx.test.backup.host.StorageDomain
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir
const val SEED_THEME = "com.example.myapp.SeedThemeAction"
const val VERIFY_THEME = "com.example.myapp.VerifyThemeAction"
@ExtendWith(BackupRestoreExtension::class)
class ThemeBackupTest {
@Test
fun themeSurvivesCloudBackup(
device: BackupRestoreController,
@TempDir outputDir: Path,
): Unit = runBlocking {
device.runBackupRestoreFlow(
storage = StorageDomain.CustomData(
populateAction = SEED_THEME,
verifyAction = VERIFY_THEME,
args = mapOf("theme" to "dark"),
),
outputDir = outputDir,
mode = BackupTransportMode.CLOUD_ENCRYPTED,
)
}
}
如需将播种期间生成的值传递给验证操作,请将映射传递给 BackupDeviceActionResult.success。CustomData 会将这些条目合并到验证操作的参数中。
运行流程的每个步骤
如需自定义流程(例如,调用 uninstallApp 和 installApk 来测试升级,或调用 launchApp),请自行运行每个步骤:
package com.example.myapp
import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir
@ExtendWith(BackupRestoreExtension::class)
class CustomFlowBackupTest {
@Test
fun themeSurvivesLaunchAfterRestore(
device: BackupRestoreController,
@TempDir outputDir: Path,
): Unit = runBlocking {
val args = mapOf("theme" to "dark")
device.runOnDevice(SEED_THEME, args).getOrThrow()
device.stopApp()
val backupFile = device.performBackup(
BackupTransportMode.CLOUD_ENCRYPTED,
outputDir,
)
device.clearAppData()
device.performRestore(backupFile)
device.launchApp()
device.runOnDevice(VERIFY_THEME, args).getOrThrow()
}
}
当设备操作失败时,runOnDevice 会返回 BackupActionResult.Failure,而不是抛出异常。调用 getOrThrow,以便在成功时获取返回的 data 映射,或在失败时抛出 IOException。
测试设备之间的转移
如需测试两台设备之间的传输,请为每台设备声明一个 BackupRestoreController 参数,并将目标设备传递给 runBackupRestoreFlow 中的 restoreOn:
package com.example.myapp
import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import androidx.test.backup.host.Device
import androidx.test.backup.host.StorageDomain
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir
@ExtendWith(BackupRestoreExtension::class)
class ThemeTransferTest {
@Test
fun themeTransfersToNewDevice(
@Device(role = "source") source: BackupRestoreController,
@Device(role = "target") target: BackupRestoreController,
@TempDir outputDir: Path,
): Unit = runBlocking {
source.runBackupRestoreFlow(
storage = StorageDomain.DataStorePreference(
dataStoreName = "settings",
key = "theme",
value = "dark",
),
outputDir = outputDir,
mode = BackupTransportMode.DEVICE_TO_DEVICE,
restoreOn = target,
)
}
}
在 source 上,该流程会为应用提供种子数据并备份应用。在 target 上,该流程会清除应用数据、恢复备份并验证数据。在其他设备上恢复需要 DEVICE_TO_DEVICE 或云传输模式,因为 LOCAL 备份数据会保留在源设备上。
@Device 的 role 值可让您在运行测试时为每个参数选择一个设备。如果只连接了一个匹配的设备,则两个参数会共用该设备,并在单个设备上模拟传输。
选择设备
默认情况下,测试框架会按序列号对在线设备进行排序,并在连接了足够多的设备时为每个 BackupRestoreController 参数提供其自己的匹配设备。无法获取单独设备的形参会共享第一个匹配的设备。ANDROID_SERIAL 环境变量不会影响设备分配。
如需在不将序列号放入测试中的情况下选择设备,请使用 @Device(role = "...") 为每个设备参数指定一个角色。然后,在运行测试时,使用 androidx.test.backup.device.serial.{role} 系统属性将每个角色绑定到设备。例如,以下命令会运行该套件,其中 emulator-5554 作为源设备,emulator-5556 作为目标设备:
./gradlew :app:testDebugBackupTestDefaultTestSuite -Dandroidx.test.backup.device.serial.source=emulator-5554 -Dandroidx.test.backup.device.serial.target=emulator-5556绑定仅适用于具有该角色的形参。其他参数会获取其余匹配的设备。
您还可以使用 @Device 上的 serial、api、minApi 和 maxApi 过滤设备,例如 @Device(role = "target", minApi = 35)。如需通过命令行设置序列号或确切的 API 级别,请使用 -D 传递以下系统属性:
| 系统属性 | 说明 |
|---|---|
androidx.test.backup.device.serial.{role} |
使用 @Device(role = "{role}") 注释的参数的设备序列号。优先于 serials、serial 和 @Device 的 serial 值。 |
androidx.test.backup.device.serials |
以逗号分隔列表形式的序列号,按顺序分配给设备参数。优先于 serial 和 @Device 的 serial 值。 |
androidx.test.backup.device.serial |
serials 未涵盖的设备参数的序列号。优先于 @Device 的 serial 值。 |
androidx.test.backup.device.apis |
API 级别逗号分隔列表,按顺序分配给设备参数。使用 0 可匹配任何 API 级别。在 minApi 和 maxApi 中,将 @Device 上的 api 替换。 |
androidx.test.backup.device.api |
apis 未涵盖的设备参数的 API 级别。使用 0 可匹配任何 API 级别。在 @Device、minApi 和 maxApi 中替换 api。 |
serial、serials、api 和 apis 属性适用于运行的每个测试,因此请使用 --tests 选项仅运行需要这些属性的测试。
测试“恢复凭证”功能
如果您的应用使用恢复凭证,请测试其恢复密钥在备份和恢复后是否仍然有效。云端和设备到设备模式会备份密钥以及应用的数据,但 LOCAL 不会。
编写设备操作,以创建密钥并使用 Credential Manager 获取密钥。创建操作会返回密钥的凭据 ID,以便验证操作可以检查恢复操作是否恢复了相同的密钥。在此示例中,createRequestJson 和 getRequestJson 是应用传递给 Credential Manager 的请求 JSON 字符串:
// In app/src/backupTest/androidTest/kotlin/com/example/myapp/RestoreKeyActions.kt
package com.example.myapp
import android.content.Context
import androidx.credentials.CreateRestoreCredentialRequest
import androidx.credentials.CreateRestoreCredentialResponse
import androidx.credentials.CredentialManager
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetRestoreCredentialOption
import androidx.credentials.RestoreCredential
import androidx.test.backup.BackupDeviceAction
import androidx.test.backup.BackupDeviceActionArgs
import androidx.test.backup.BackupDeviceActionResult
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
class CreateRestoreKeyAction : BackupDeviceAction {
override fun execute(
context: Context,
args: BackupDeviceActionArgs,
): BackupDeviceActionResult {
val request = CreateRestoreCredentialRequest(createRequestJson)
val response = runBlocking {
CredentialManager.create(context).createCredential(context, request)
} as CreateRestoreCredentialResponse
val credentialId = JSONObject(response.responseJson).getString("id")
return BackupDeviceActionResult.success(mapOf("credential_id" to credentialId))
}
}
class VerifyRestoreKeyAction : BackupDeviceAction {
override fun execute(
context: Context,
args: BackupDeviceActionArgs,
): BackupDeviceActionResult {
val option = GetRestoreCredentialOption(getRequestJson)
val response = runBlocking {
CredentialManager.create(context)
.getCredential(context, GetCredentialRequest(listOf(option)))
}
val credential = response.credential as RestoreCredential
val credentialId = JSONObject(credential.authenticationResponseJson).getString("id")
val expected = args["credential_id"]
return if (credentialId == expected) {
BackupDeviceActionResult.success()
} else {
BackupDeviceActionResult.failure("Expected key $expected, found $credentialId")
}
}
}
以下主机测试会在源设备上创建密钥,在目标设备上恢复密钥,并验证凭据 ID 是否匹配:
package com.example.myapp
import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import androidx.test.backup.host.Device
import androidx.test.backup.host.StorageDomain
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir
const val CREATE_RESTORE_KEY = "com.example.myapp.CreateRestoreKeyAction"
const val VERIFY_RESTORE_KEY = "com.example.myapp.VerifyRestoreKeyAction"
@ExtendWith(BackupRestoreExtension::class)
class RestoreKeyTest {
@Test
fun restoreKeyTransfersToNewDevice(
@Device(role = "source") source: BackupRestoreController,
@Device(role = "target") target: BackupRestoreController,
@TempDir outputDir: Path,
): Unit = runBlocking {
// Clears target (including any existing restore key) before restoring.
source.runBackupRestoreFlow(
storage = StorageDomain.CustomData(
populateAction = CREATE_RESTORE_KEY,
verifyAction = VERIFY_RESTORE_KEY,
),
outputDir = outputDir,
mode = BackupTransportMode.DEVICE_TO_DEVICE,
restoreOn = target,
)
}
}
如需创建将 isCloudBackupEnabled 设为 true(默认值)的密钥,源设备需要有一个已开启备份功能和屏幕锁定的 Google 账号。
否则,Credential Manager 会抛出 E2eeUnavailableException。
与在新设备上进行云端恢复不同,云端模式不需要使用同一 Google 账号或来源设备的屏幕锁定,并且会恢复在 isCloudBackupEnabled 设置为 false 的情况下创建的密钥。
在测试之间保留应用数据
在每次测试之前,测试框架都会清除测试所用每台设备上的应用数据。如需为测试类或方法停用此功能,请使用 @Isolation(IsolationPolicy.MANUAL) 为其添加注解。
使用 Java 编写测试
BackupRestoreController 方法是 Kotlin 挂起函数。如果您使用 Java 编写主机测试,请使用每种方法的 Async 变体(例如 runBackupRestoreFlowAsync),该变体会返回 ListenableFuture。
运行备份和恢复测试
AGP 会为套件测试的每个变体创建一个名为 test{Variant}{SuiteName}DefaultTestSuite 的 Gradle 任务。如需针对 debug 变体运行 backupTest 套件,请连接您的设备并运行以下任务:
./gradlew :app:testDebugBackupTestDefaultTestSuitecheck、test 和 connectedCheck 任务不会运行备份和恢复测试套件,因此请明确运行套件的任务。
如需运行单个测试类或方法,请使用 --tests 选项:
./gradlew :app:testDebugBackupTestDefaultTestSuite --tests "com.example.myapp.ThemeBackupTest"检查测试报告
测试运行完成后,AGP 会在 {module}/build/reports/tests/{taskName}/index.html(例如 app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html)生成 HTML 测试报告。
当测试失败时,测试框架会保存测试所用每个设备的 logcat,并在失败输出中列出保存的文件路径。如需关闭日志保存功能,请传递 -Dandroidx.test.backup.device.logs.enabled=false。
排查恢复失败问题
在以下情况下,云端恢复或设备间恢复会失败,并显示出错提示:
- Google Play 服务跳过您的应用,例如,因为其应用 ID 以
com.google.或com.android.开头、其备份为空,或者它设置了android:allowBackup="false"。如需测试具有此类应用 ID 的应用,请使用LOCAL模式。 - 在搭载 API 级别 36.1 或更高级别的设备或模拟器上,Google Play 服务版本低于 26.33.30。通过设备上的 Play 商店更新 Google Play 服务,或使用附带 26.33.30 或更高版本的系统映像。
已知问题和限制
自动备份和恢复测试具有以下已知问题和限制:
- Android Studio 尚不支持测试套件。即使项目配置正确,代码编辑器也可能会在测试套件源文件中显示未解析的引用错误。请改用 Gradle 从命令行构建和运行备份与恢复测试。
- 测试仅在通过 adb 连接到开发机器的设备和模拟器上运行。不支持 Firebase Test Lab。
- 测试会清除应用的数据,并更改所用设备上的备份设置。我们建议使用专用测试设备。
如需了解每个版本中的变更,请参阅备份和恢复测试版本说明。