Cuando los usuarios configuran un dispositivo nuevo o reinstalan tu app, Android puede restablecer los datos de la app desde una copia de seguridad en la nube o una transferencia de dispositivo a dispositivo. Si tus reglas de copia de seguridad omiten datos importantes o tu app no puede usar los datos que restablece, los usuarios perderán su configuración y progreso sin ver un error.
Las pruebas automatizadas de copia de seguridad y restablecimiento te ayudan a detectar estos problemas antes de lanzar la app. Son pruebas de JUnit 5 que se ejecutan en tu máquina de desarrollo y controlan los dispositivos conectados a través de Android Debug Bridge (adb). Cada prueba propaga datos de la app en un dispositivo, crea una copia de seguridad y restablece la app a través del framework de copia de seguridad de Android, y, luego, verifica que los datos estén intactos. Configura las pruebas como un paquete de pruebas del complemento de Android para Gradle (AGP) en el archivo de compilación de tu módulo.
Cómo funciona
Un paquete de pruebas de copia de seguridad y restablecimiento tiene dos partes que funcionan juntas:
- Las pruebas de host son pruebas de JUnit 5 que se ejecutan en tu máquina de desarrollo. Usan un
BackupRestoreControllerpara cada dispositivo con el objetivo de propagar datos, crear copias de seguridad y restablecer tu app, y verificar los resultados. - Las acciones del dispositivo son clases que implementan
BackupDeviceAction. Se ejecutan en el proceso de tu app en el dispositivo, por lo que pueden leer y escribir datos a través del código de tu app. Las pruebas del host llaman a las acciones del dispositivo por nombre de clase.
Cuando ejecutas el paquete, el AGP compila tu app y un APK de prueba que contiene tus acciones del dispositivo. El framework de pruebas instala ambos APK en cada dispositivo que usan las pruebas y borra los datos de tu app antes de cada prueba.
Tipos de almacenamiento integrado
En la siguiente tabla, se enumeran las subclases de StorageDomain que te permiten propagar y verificar datos sin escribir acciones del dispositivo:
| Dominio de almacenamiento | Datos |
|---|---|
StorageDomain.Preference |
Una clave en un archivo SharedPreferences. |
StorageDomain.DataStorePreference |
Es una clave en un archivo de Preferences DataStore. |
StorageDomain.Database |
Es una fila en una tabla de base de datos SQLite. |
StorageDomain.TextFile |
Un archivo de texto en el almacenamiento interno de tu app |
StorageDomain.BinaryFile |
Un archivo binario en el almacenamiento interno de tu app |
StorageDomain.ExcludedData |
Otro dominio de almacenamiento integrado cuyos datos no se deben restablecer, como los datos excluidos por tus reglas de copia de seguridad. |
Para probar otros datos, como los archivos de Proto DataStore o las bases de datos de Room, escribe acciones del dispositivo.
Medios de transporte
Cada copia de seguridad usa un valor BackupTransportMode. En la siguiente tabla, se enumeran los modos de transporte admitidos:
| Modo | Situación |
|---|---|
LOCAL |
Una copia de seguridad en el transporte de copia de seguridad local del dispositivo. Los datos de la copia de seguridad permanecen en el dispositivo. |
CLOUD_ENCRYPTED |
Una copia de seguridad en la nube, como una que se guarda en la Cuenta de Google del usuario desde un dispositivo con encriptación del cliente |
CLOUD_UNENCRYPTED |
Una copia de seguridad en la nube de un dispositivo que no tiene encriptación del cliente |
DEVICE_TO_DEVICE |
Una transferencia de dispositivo a dispositivo, por ejemplo, a través de un cable o Wi-Fi |
LOCAL funciona en cualquier dispositivo compatible. Los modos de nube y de dispositivo a dispositivo crean las mismas copias de seguridad que la acción Backup App Data en Android Studio: la copia de seguridad se guarda en tu máquina de desarrollo en lugar de subirse o enviarse a otro dispositivo. Los modos de nube y de dispositivo a dispositivo requieren los Servicios de Google Play en el dispositivo y una compilación depurable de tu app. Si tus reglas de copia de seguridad dependen de la encriptación del cliente, prueba ambos modos de nube.
Requisitos
Para usar las pruebas automatizadas de copia de seguridad y restablecimiento, asegúrate de que tu entorno cumpla con los siguientes requisitos:
- Gradle 9.6.0 o una versión posterior
- JDK versión 17 o posterior
- Un dispositivo o emulador que ejecute Android 12 (nivel de API 31) o una versión posterior, y que esté conectado a través de adb:
- Los dispositivos o emuladores con el nivel de API 36.1 o posterior requieren la versión 26.33.30 o posterior de los Servicios de Google Play.
- Para probar las transferencias entre dos dispositivos, conecta dos dispositivos.
Compatibilidad
En la siguiente tabla, se enumeran las versiones mínimas y recomendadas para las pruebas automatizadas de copia de seguridad y restablecimiento:
| Componente | Versión mínima | Versión recomendada |
|---|---|---|
| Android Studio | Rabbit 2 Canary 1 | Rabbit 2 Canary 4 o una versión posterior |
| Complemento de Android para Gradle (AGP) | 9.5.0-alpha06 | 9.5.0-alpha09 o una versión posterior |
androidx.test.backup bibliotecas |
1.0.0-alpha01 | 1.0.0-alpha04 o una versión posterior |
Cada versión preliminar del AGP requiere la versión preliminar correspondiente de Android Studio. Para obtener más información, consulta Versiones preliminares del AGP.
Configura tu proyecto
Para agregar un paquete de pruebas de copia de seguridad y restablecimiento al módulo de tu app, completa los siguientes pasos:
Habilita las marcas experimentales
En el archivo gradle.properties de tu proyecto, habilita los paquetes de pruebas y las pruebas de copia de seguridad:
android.experimental.testSuiteSupport=true
android.experimental.enableBackupTest=true
Declara el paquete de pruebas
En el archivo build.gradle.kts del módulo de tu app, declara un paquete de pruebas de copia de seguridad en
el bloque testOptions:
android {
testOptions {
backupTests {
create("backupTest") {
backupTestLibraryVersion = "1.0.0-alpha04"
}
}
}
}
El nombre del paquete, backupTest en este ejemplo, determina los directorios del conjunto de orígenes y los nombres de las tareas de Gradle. Puedes personalizar el paquete con las
siguientes propiedades opcionales:
- Usa
backupTestLibraryVersionpara establecer la versión de las bibliotecas deandroidx.test.backupque el AGP agrega al paquete, como"1.0.0-alpha04". Esta propiedad es opcional. Si la omites, AGP recurre a una versión de biblioteca predeterminada según la versión de AGP. Los ejemplos de esta página requieren la versión 1.0.0-alpha04 o una posterior. - Usa
targetVariantspara especificar las variantes de compilación que prueba el paquete, comotargetVariants.add("demoDebug"). Esta propiedad es opcional. Si la omites, el paquete probará las variantes de latestBuildTypede tu módulo, que esdebugde forma predeterminada. Te recomendamos que pruebes una variante depurable, ya que los modos de transporte de nube y de dispositivo a dispositivo requieren una app depurable.
El AGP agrega la biblioteca de host, androidx.test.backup:backup-host y JUnit 5 a las pruebas de host. Agrega la biblioteca de dispositivos, androidx.test.backup:backup, al APK de prueba y configura el ejecutor de instrumentación del APK de prueba.
Para agregar otras dependencias, usa los bloques hostJar y testApk del paquete.
Por ejemplo, para usar Truth en tus pruebas de host, agrégalo al bloque hostJar:
create("backupTest") {
hostJar {
dependencies {
implementation("com.google.truth:truth:1.4.2")
}
}
}
Crea los conjuntos de orígenes
Crea directorios de conjuntos de orígenes que coincidan con el nombre del paquete:
- Coloca las pruebas del host en
src/{suiteName}/test/kotlin/. - Coloca las acciones del dispositivo en
src/{suiteName}/androidTest/kotlin/.
Por ejemplo, un paquete llamado backupTest usa la siguiente estructura de directorio:
app/src/backupTest/
├── androidTest/
│ └── kotlin/com/example/myapp/ThemeActions.kt
└── test/
└── kotlin/com/example/myapp/SettingsBackupTest.kt
Verifica la configuración de la copia de seguridad
Las pruebas crean una copia de seguridad y restablecen los datos que incluye la configuración de copia de seguridad de tu app. Asegúrate de que la copia de seguridad esté habilitada para tu app y que tus reglas de copia de seguridad incluyan los datos que pruebes.
Escribe pruebas de copia de seguridad y restablecimiento
Escribe pruebas de host en el conjunto de orígenes test del paquete. Anota cada clase de prueba con
@ExtendWith(BackupRestoreExtension::class), que proporciona dispositivos conectados
a las pruebas. Las pruebas se ejecutan en la variante de la app a la que se orienta el paquete, por lo que no es necesario que configures su ID de aplicación.
Cada método de prueba declara un parámetro BackupRestoreController para cada dispositivo que usa.
Prueba los tipos de almacenamiento integrados
Para probar los tipos de almacenamiento integrados, llama a runBackupRestoreFlow con los datos que se propagarán, un directorio para el archivo de copia de seguridad y un modo de transporte. El método ejecuta los siguientes pasos en el dispositivo:
- Propaga cada dominio de almacenamiento.
- Detiene la app.
- Crea una copia de seguridad de la app.
- Borra los datos de la app.
- Restablece la copia de seguridad.
- Verifica que cada dominio de almacenamiento tenga el valor semilla o esté ausente cuando se une en
StorageDomain.ExcludedData.
Si un paso falla, el método arroja una excepción, lo que hace que la prueba falle. En el siguiente ejemplo, se prueban SharedPreferences, Preferences DataStore, SQLite, un archivo interno y un archivo de exclusión con el transporte de copia de seguridad local:
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,
)
}
}
Ten en cuenta lo siguiente cuando definas los dominios de almacenamiento:
Preferencetoma el nombre del archivoSharedPreferencessin la extensión.xml.DataStorePreferencetoma el nombre que se pasa apreferencesDataStore. Usa el mismo tipo de Kotlin paravalueque la clave de preferencia de tu app, como1.25fparafloatPreferencesKey, de modo que tu app pueda leer la entrada restablecida.TextFileyBinaryFileresuelven las rutas de acceso relativas en función delfilesDirde tu app.Databaseescribe la fila con SQL y crea la tabla con columnasTEXTsi la tabla no existe. Si tu app usa Room, prueba el contenido de la base de datos con acciones del dispositivo que usen tus DAO para que la base de datos coincida con el esquema de Room.ExcludedDatarequiere al menos un dominio de almacenamiento no excluido en la misma llamada arunBackupRestoreFlow.Preference,DataStorePreference,Database,TextFileyBinaryFilese orientan al almacenamiento protegido por el dispositivo en lugar del almacenamiento predeterminado de tu app cuando configurasisDeviceProtectedStorage = true.
Escribir acciones del dispositivo
Para propagar o verificar datos que no cubren los tipos de almacenamiento integrados, escribe
acciones del dispositivo en el conjunto de orígenes androidTest del paquete. Implementa BackupDeviceAction y anula execute para ejecutar la acción con el Context de tu app y los argumentos de la prueba del host. Muestra un
BackupDeviceActionResult creado con
BackupDeviceActionResult.success o BackupDeviceActionResult.failure,
y pasa un mensaje de error a failure.
El método execute se ejecuta en el proceso de tu app en un subproceso en segundo plano, por lo que puede bloquearse hasta que finalicen las operaciones de lectura y escritura. Para acceder a la instrumentación del ejecutor de pruebas, como UiAutomation, llama a InstrumentationRegistry.getInstrumentation.
En el siguiente ejemplo, se inicializa y verifica un parámetro de configuración de tema en un Proto DataStore, en el que settingsDataStore es una propiedad DataStore<Settings> definida en tu app:
// 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")
}
}
}
Para ejecutar acciones de propagación y verificación en runBackupRestoreFlow, pasa sus nombres de clase a 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,
)
}
}
Para pasar los valores generados durante la propagación a la acción de verificación, pasa un mapa a
BackupDeviceActionResult.success. CustomData combina esas entradas en los
argumentos de la acción de verificación.
Ejecuta cada paso del flujo
Para personalizar el flujo, por ejemplo, llamar a uninstallApp y installApk para probar una actualización, o llamar a launchApp, ejecuta cada paso por tu cuenta:
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()
}
}
Cuando falla una acción del dispositivo, runOnDevice devuelve un BackupActionResult.Failure en lugar de arrojar una excepción.
Llama a getOrThrow para obtener el mapa data que se devuelve
en caso de éxito o arroja una IOException en caso de error.
Prueba las transferencias entre dispositivos
Para probar una transferencia entre dos dispositivos, declara un parámetro BackupRestoreController para cada dispositivo y pasa el dispositivo de destino a restoreOn en runBackupRestoreFlow:
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,
)
}
}
En source, el flujo inicializa y crea una copia de seguridad de la app. En target, borra los datos de la app, restablece la copia de seguridad y verifica los datos. Para restablecer los datos en otro dispositivo,
se requiere DEVICE_TO_DEVICE o un modo de transporte en la nube, ya que los datos de la copia de seguridad de LOCAL
permanecen en el dispositivo de origen.
Los valores role de @Device te permiten
elegir un dispositivo para cada parámetro cuando ejecutas las pruebas. Si solo hay un dispositivo coincidente conectado, ambos parámetros comparten ese dispositivo y simulan la transferencia en un solo dispositivo.
Elegir dispositivos
De forma predeterminada, el framework de pruebas ordena los dispositivos en línea por número de serie y asigna
a cada parámetro BackupRestoreController su propio dispositivo coincidente cuando hay suficientes
dispositivos conectados. Los parámetros que no pueden obtener un dispositivo independiente comparten el primer dispositivo coincidente. La variable de entorno ANDROID_SERIAL no afecta la asignación de dispositivos.
Para elegir dispositivos sin colocar números de serie en tus pruebas, asigna un rol a cada parámetro de dispositivo con @Device(role = "..."). Luego, cuando ejecutes las pruebas, vincula cada rol a un dispositivo con la propiedad del sistema androidx.test.backup.device.serial.{role}. Por ejemplo, el siguiente comando ejecuta el paquete con emulator-5554 como dispositivo de origen y emulator-5556 como dispositivo de destino:
./gradlew :app:testDebugBackupTestDefaultTestSuite -Dandroidx.test.backup.device.serial.source=emulator-5554 -Dandroidx.test.backup.device.serial.target=emulator-5556Una vinculación solo se aplica a los parámetros con ese rol. Otros parámetros obtienen los dispositivos coincidentes restantes.
También puedes filtrar dispositivos con serial, api, minApi y maxApi en
@Device, como @Device(role = "target", minApi = 35). Para establecer números de serie o niveles de API exactos desde la línea de comandos, pasa las siguientes propiedades del sistema con -D:
| Propiedad del sistema | Descripción |
|---|---|
androidx.test.backup.device.serial.{role} |
Es el número de serie del dispositivo para los parámetros anotados con @Device(role = "{role}"). Tiene prioridad sobre serials, serial y el valor serial de @Device. |
androidx.test.backup.device.serials |
Es una lista separada por comas de números de serie, asignados a los parámetros del dispositivo en orden. Tiene prioridad sobre serial y el valor serial de @Device. |
androidx.test.backup.device.serial |
El número de serie de los parámetros del dispositivo que no cubre serials. Tiene prioridad sobre el valor serial de @Device. |
androidx.test.backup.device.apis |
Es una lista de niveles de API separados por comas, asignados a los parámetros del dispositivo en orden. Usa 0 para que coincida con cualquier nivel de API. Reemplaza api por @Device, dentro de minApi y maxApi. |
androidx.test.backup.device.api |
Es el nivel de API para los parámetros del dispositivo que no cubre apis. Usa 0 para que coincida con cualquier nivel de API. Reemplaza api por @Device, dentro de minApi y maxApi. |
Las propiedades serial, serials, api y apis se aplican a cada prueba que se ejecuta, por lo que debes usar la opción --tests para ejecutar solo las pruebas que las necesitan.
Prueba las credenciales de restablecimiento
Si tu app usa Restore Credentials, prueba que su clave de restablecimiento sobreviva a una copia de seguridad y un restablecimiento. Los modos de nube y de dispositivo a dispositivo crean una copia de seguridad de la clave con los datos de tu app, pero LOCAL no lo hace.
Escribe acciones del dispositivo que creen la clave y la obtengan con Credential Manager. La
acción de creación devuelve el ID de credencial de la clave para que la acción de verificación pueda
comprobar que el restablecimiento devuelva la misma clave. En este ejemplo,
createRequestJson y getRequestJson son las cadenas JSON de solicitud que tu
app pasa al Administrador de credenciales:
// 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")
}
}
}
La siguiente prueba de host crea la clave en el dispositivo de origen, la restablece en el dispositivo de destino y verifica que el ID de credencial coincida:
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,
)
}
}
Para crear una clave con isCloudBackupEnabled configurado como true, el valor predeterminado, el dispositivo de origen necesita una Cuenta de Google con la copia de seguridad activada y un bloqueo de pantalla.
De lo contrario, el Administrador de credenciales arroja E2eeUnavailableException.
A diferencia de un restablecimiento en la nube en un dispositivo nuevo, los modos de nube no requieren la misma
Cuenta de Google ni el bloqueo de pantalla del dispositivo de origen, y restablecen las claves creadas
con isCloudBackupEnabled establecido en false.
Conserva los datos de la app entre pruebas
Antes de cada prueba, el framework de pruebas borra los datos de tu app en cada dispositivo que
usa la prueba. Para desactivar esta opción en una clase o un método de prueba, anótala con @Isolation(IsolationPolicy.MANUAL).
Escribe pruebas en Java
Los métodos BackupRestoreController son funciones de suspensión de Kotlin. Si escribes pruebas de host en Java, usa la variante Async de cada método, como runBackupRestoreFlowAsync, que devuelve un ListenableFuture.
Ejecuta pruebas de copia de seguridad y restablecimiento
AGP crea una tarea de Gradle para cada variante que prueba el paquete, llamada test{Variant}{SuiteName}DefaultTestSuite. Para ejecutar el paquete backupTest en la variante debug, conecta tus dispositivos y ejecuta la tarea:
./gradlew :app:testDebugBackupTestDefaultTestSuiteLas tareas check, test y connectedCheck no ejecutan paquetes de pruebas de copia de seguridad y restablecimiento, por lo que debes ejecutar la tarea del paquete de forma explícita.
Para ejecutar una sola clase o un método de prueba, usa la
opción --tests:
./gradlew :app:testDebugBackupTestDefaultTestSuite --tests "com.example.myapp.ThemeBackupTest"Inspecciona los informes de pruebas
Una vez que se completa una ejecución de prueba, el AGP genera un informe de prueba en formato HTML en {module}/build/reports/tests/{taskName}/index.html, como app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html.
Cuando una prueba falla, el framework de pruebas guarda el logcat de cada dispositivo que usó la prueba y enumera las rutas de acceso de los archivos guardados en el resultado de la falla. Para desactivar el guardado de registros, pasa -Dandroidx.test.backup.device.logs.enabled=false.
Soluciona problemas de restablecimientos con errores
Un restablecimiento de la nube o de dispositivo a dispositivo falla con un mensaje de error en los siguientes casos:
- Los Servicios de Google Play omiten tu app, por ejemplo, porque su ID de aplicación comienza con
com.google.ocom.android., su copia de seguridad está vacía o estableceandroid:allowBackup="false". Para probar una app con ese ID de aplicación, usa el modoLOCAL. - La versión de los Servicios de Google Play es anterior a la 26.33.30 en un dispositivo o emulador que ejecuta el nivel de API 36.1 o una versión posterior. Actualiza los Servicios de Google Play a través de Play Store en el dispositivo o usa una imagen del sistema que incluya la versión 26.33.30 o una posterior.
Problemas conocidos y limitaciones
Las pruebas automatizadas de copia de seguridad y restablecimiento tienen los siguientes problemas conocidos y limitaciones:
- La compatibilidad de Android Studio con los paquetes de pruebas aún no está disponible. Es posible que el editor de código muestre errores de referencia sin resolver en los archivos fuente del paquete de pruebas, incluso cuando el proyecto esté configurado correctamente. En su lugar, compila y ejecuta pruebas de copia de seguridad y restablecimiento desde la línea de comandos con Gradle.
- Las pruebas se ejecutan solo en dispositivos y emuladores que están conectados a tu máquina de desarrollo a través de adb. No se admite Firebase Test Lab.
- Las pruebas borran los datos de tu app y cambian la configuración de las copias de seguridad en los dispositivos que usan. Te recomendamos que uses dispositivos de prueba exclusivos.
Para conocer los cambios en cada versión, consulta las notas de la versión de prueba de copia de seguridad y restablecimiento.