Teste automatizado de backup e restauração

Quando os usuários configuram um novo dispositivo ou reinstalam seu app, o Android pode restaurar os dados do app de um backup na nuvem ou de uma transferência entre dispositivos. Se as regras de backup deixarem de fora dados importantes ou se o app não puder usar os dados restaurados, os usuários vão perder as configurações e o progresso sem ver um erro.

Os testes automatizados de backup e restauração ajudam a detectar esses problemas antes de lançar o app. Eles são testes do JUnit 5 que são executados na máquina de desenvolvimento e controlam os dispositivos conectados usando o Android Debug Bridge (adb). Cada teste propaga dados do app em um dispositivo, faz backup e restaura o app usando o framework de backup do Android e, em seguida, verifica se os dados estão intactos. Configure os testes como um pacote de testes do Plug-in do Android para Gradle (AGP) no arquivo de build do módulo.

Como funciona

Um pacote de testes de backup e restauração tem duas partes que funcionam juntas:

  • Os testes de host são testes do JUnit 5 executados na sua máquina de desenvolvimento. Eles usam um BackupRestoreController para cada dispositivo para propagar dados, fazer backup e restaurar o app e verificar os resultados.
  • As ações do dispositivo são classes que implementam BackupDeviceAction. Eles são executados no processo do app no dispositivo, para que possam ler e gravar dados usando o próprio código do app. Os testes de host chamam ações do dispositivo pelo nome da classe.

Quando você executa o pacote, o AGP cria seu app e um APK de teste que contém as ações no dispositivo. O framework de teste instala os dois APKs em cada dispositivo usado pelos testes e limpa os dados do app antes de cada teste.

Tipos de armazenamento integrado

A tabela a seguir lista as subclasses StorageDomain que permitem propagar e verificar dados sem escrever ações no dispositivo:

Domínio de armazenamento Dados
StorageDomain.Preference Uma chave em um arquivo SharedPreferences.
StorageDomain.DataStorePreference Uma chave em um arquivo do Preferences DataStore.
StorageDomain.Database Uma linha em uma tabela de banco de dados SQLite.
StorageDomain.TextFile Um arquivo de texto no armazenamento interno do app.
StorageDomain.BinaryFile Um arquivo binário no armazenamento interno do app.
StorageDomain.ExcludedData Outro domínio de armazenamento integrado com dados que não devem ser restaurados, como dados excluídos pelas regras de backup.

Para testar outros dados, como arquivos do Proto DataStore ou bancos de dados do Room, escreva ações do dispositivo.

Meios de transporte

Cada backup usa um valor BackupTransportMode. A tabela a seguir lista os meios de transporte aceitos:

Modo Cenário
LOCAL Um backup para o transporte de backup local do dispositivo. Os dados de backup permanecem no dispositivo.
CLOUD_ENCRYPTED Um backup na nuvem, como um salvo na Conta do Google do usuário em um dispositivo com criptografia do lado do cliente.
CLOUD_UNENCRYPTED Um backup na nuvem de um dispositivo que não tem criptografia do lado do cliente.
DEVICE_TO_DEVICE Uma transferência de dispositivo para dispositivo, por exemplo, por cabo ou Wi-Fi.

O LOCAL funciona em qualquer dispositivo compatível. Os modos de nuvem e de dispositivo para dispositivo criam os mesmos backups que a ação Fazer backup dos dados do app no Android Studio: o backup é salvo na máquina de desenvolvimento em vez de ser enviado por upload ou para outro dispositivo. Os modos de nuvem e de dispositivo para dispositivo exigem o Google Play Services no dispositivo e um build depurável do app. Se as regras de backup dependerem da criptografia do lado do cliente, teste os dois modos de nuvem.

Requisitos

Para usar o teste automatizado de backup e restauração, verifique se o ambiente atende aos seguintes requisitos:

  • Gradle versão 9.6.0 ou mais recente.
  • JDK versão 17 ou mais recente.
  • Um dispositivo ou emulador com o Android 12 (nível 31 da API) ou mais recente conectado pelo adb:
    • Dispositivos ou emuladores no nível 36.1 ou mais recente da API exigem o Google Play Services versão 26.33.30 ou mais recente.
    • Para testar transferências entre dois dispositivos, conecte-os.

Compatibilidade

A tabela a seguir lista as versões mínimas e recomendadas para testes automatizados de backup e restauração:

Componente Versão mínima Versão recomendada
Android Studio Rabbit 2 Canary 1 Rabbit 2 Canary 4 ou mais recente
Plug-in do Android para Gradle (AGP) 9.5.0-alpha06 9.5.0-alpha09 ou mais recente
androidx.test.backup bibliotecas 1.0.0-alpha01 1.0.0-alpha04 ou mais recente

Cada versão de pré-lançamento do AGP requer a versão de pré-lançamento correspondente do Android Studio. Para mais informações, consulte Pré-lançamentos do AGP.

Criar o projeto

Para adicionar um pacote de testes de backup e restauração ao módulo do app, siga estas etapas:

Ativar flags experimentais

No arquivo gradle.properties do projeto, ative os pacotes de testes e os testes de backup:

android.experimental.testSuiteSupport=true
android.experimental.enableBackupTest=true

Declarar o pacote de testes

No arquivo build.gradle.kts do módulo do app, declare um pacote de testes de backup no bloco testOptions:

android {
    testOptions {
        backupTests {
            create("backupTest") {
                backupTestLibraryVersion = "1.0.0-alpha04"
            }
        }
    }
}

O nome do pacote, backupTest neste exemplo, determina os diretórios do conjunto de origem e os nomes das tarefas do Gradle. É possível personalizar o pacote com as seguintes propriedades opcionais:

  • Use backupTestLibraryVersion para definir a versão das bibliotecas androidx.test.backup que o AGP adiciona ao pacote, como "1.0.0-alpha04". Esta propriedade é opcional. Se você omitir, o AGP vai usar uma versão de biblioteca padrão com base na versão do AGP. Os exemplos nesta página exigem a versão 1.0.0-alpha04 ou mais recente.
  • Use targetVariants para especificar as variantes de build que o pacote testa, como targetVariants.add("demoDebug"). Esta propriedade é opcional. Se você omitir, o pacote vai testar as variantes do testBuildType do seu módulo, que é debug por padrão. Recomendamos testar uma variante depurável, porque os modos de transporte em nuvem e de dispositivo para dispositivo exigem um app depurável.

O AGP adiciona a biblioteca de host, androidx.test.backup:backup-host, e o JUnit 5 aos testes de host. Ele adiciona a biblioteca de dispositivos, androidx.test.backup:backup, ao APK de teste e define o executor de instrumentação do APK de teste.

Para adicionar outras dependências, use os blocos hostJar e testApk do pacote. Por exemplo, para usar o Truth nos testes de host, adicione-o ao bloco hostJar:

create("backupTest") {
    hostJar {
        dependencies {
            implementation("com.google.truth:truth:1.4.2")
        }
    }
}

Criar os conjuntos de origem

Crie diretórios de conjuntos de origem que correspondam ao nome do pacote:

  • Coloque os testes de host em src/{suiteName}/test/kotlin/.
  • Coloque as ações do dispositivo em src/{suiteName}/androidTest/kotlin/.

Por exemplo, um pacote chamado backupTest usa a seguinte estrutura de diretórios:

app/src/backupTest/
├── androidTest/
│   └── kotlin/com/example/myapp/ThemeActions.kt
└── test/
    └── kotlin/com/example/myapp/SettingsBackupTest.kt

Verificar a configuração de backup

Os testes fazem backup e restauram os dados incluídos na configuração de backup do app. Verifique se o backup está ativado para o app e se as regras de backup incluem os dados que você testa.

Criar testes de backup e restauração

Escreva testes de host no conjunto de origem test do pacote. Adicione a anotação @ExtendWith(BackupRestoreExtension::class) a cada classe de teste, que fornece dispositivos conectados aos testes. Os testes são executados na variante do app que o pacote segmenta. Portanto, não é necessário definir o ID do aplicativo.

Cada método de teste declara um parâmetro BackupRestoreController para cada dispositivo que ele usa.

Testar tipos de armazenamento integrados

Para testar os tipos de armazenamento integrados, chame runBackupRestoreFlow com os dados a serem propagados, um diretório para o arquivo de backup e um modo de transporte. O método executa as seguintes etapas no dispositivo:

  1. Propaga cada domínio de armazenamento.
  2. Interrompe o app.
  3. Faz backup do app.
  4. Limpa os dados do app.
  5. Restaura o backup.
  6. Verifica se cada domínio de armazenamento tem o valor de semente ou se está ausente quando encapsulado em StorageDomain.ExcludedData.

Se uma etapa falhar, o método vai gerar uma exceção, o que vai causar a falha do teste. O exemplo a seguir testa SharedPreferences, o DataStore de preferências, o SQLite, um arquivo interno e um arquivo de exclusão usando o transporte de backup 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,
        )
    }
}

Lembre-se do seguinte ao definir domínios de armazenamento:

  • Preference usa o nome do arquivo SharedPreferences sem a extensão .xml.
  • DataStorePreference usa o nome transmitido para preferencesDataStore. Use o mesmo tipo do Kotlin para value que a chave de preferência do app, como 1.25f para floatPreferencesKey, para que o app possa ler a entrada restaurada.
  • TextFile e BinaryFile resolvem caminhos relativos em relação ao filesDir do app.
  • Database grava a linha com SQL e cria a tabela com colunas TEXT se ela não existir. Se o app usa o Room, teste o conteúdo do banco de dados com ações do dispositivo que usam seus DAOs para que o banco de dados corresponda ao esquema do Room.
  • ExcludedData requer pelo menos um domínio de armazenamento não excluído na mesma chamada runBackupRestoreFlow.
  • Preference, DataStorePreference, Database, TextFile e BinaryFile segmentam o armazenamento protegido por dispositivo em vez do armazenamento padrão do app quando você define isDeviceProtectedStorage = true.

Ações de gravação do dispositivo

Para propagar ou verificar dados que os tipos de armazenamento integrados não cobrem, escreva ações do dispositivo no conjunto de origem androidTest do pacote. Implemente BackupDeviceAction e substitua execute para executar a ação com o Context do app e os argumentos do teste de host. Retorne um BackupDeviceActionResult criado com BackupDeviceActionResult.success ou BackupDeviceActionResult.failure, transmitindo uma mensagem de erro para failure.

O método execute é executado no processo do app em uma linha de execução em segundo plano. Portanto, ele pode bloquear até que as leituras e gravações sejam concluídas. Para acessar a instrumentação do executor de teste, como UiAutomation, chame InstrumentationRegistry.getInstrumentation.

O exemplo abaixo define e verifica uma configuração de tema em um Proto DataStore, em que settingsDataStore é uma propriedade DataStore<Settings> definida no 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 executar ações de preenchimento e verificação em runBackupRestoreFlow, transmita os nomes de classe para 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 transmitir valores gerados durante a propagação para a ação de verificação, transmita um mapa para BackupDeviceActionResult.success. CustomData mescla essas entradas nos argumentos da ação de verificação.

Executar cada etapa do fluxo

Para personalizar o fluxo, por exemplo, chamando uninstallApp e installApk para testar um upgrade ou chamando launchApp, execute cada etapa por conta própria:

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()
    }
}

Quando uma ação do dispositivo falha, runOnDevice retorna um BackupActionResult.Failure em vez de gerar uma exceção. Chame getOrThrow para receber o mapa data retornado em caso de sucesso ou gere um IOException em caso de falha.

Testar transferências entre dispositivos

Para testar uma transferência entre dois dispositivos, declare um parâmetro BackupRestoreController para cada dispositivo e transmita o dispositivo de destino para restoreOn em 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,
        )
    }
}

No source, o fluxo gera e faz backup do app. No target, ele limpa os dados do app, restaura o backup e verifica os dados. A restauração em outro dispositivo requer o DEVICE_TO_DEVICE ou um modo de transporte em nuvem, porque os dados de backup do LOCAL permanecem no dispositivo de origem.

Os valores role de @Device permitem que você escolha um dispositivo para cada parâmetro ao executar os testes. Se apenas um dispositivo correspondente estiver conectado, os dois parâmetros vão compartilhar esse dispositivo e simular a transferência em um único dispositivo.

Escolher dispositivos

Por padrão, o framework de teste classifica os dispositivos on-line por número de série e atribui a cada parâmetro BackupRestoreController o próprio dispositivo correspondente quando há dispositivos suficientes conectados. Os parâmetros que não podem receber um dispositivo separado compartilham o primeiro dispositivo correspondente. A variável de ambiente ANDROID_SERIAL não afeta a atribuição de dispositivos.

Para escolher dispositivos sem colocar números de série nos testes, atribua uma função a cada parâmetro de dispositivo com @Device(role = "..."). Em seguida, ao executar os testes, vincule cada função a um dispositivo com a propriedade do sistema androidx.test.backup.device.serial.{role}. Por exemplo, o comando a seguir executa o pacote com emulator-5554 como o dispositivo de origem e emulator-5556 como o dispositivo de destino:

./gradlew :app:testDebugBackupTestDefaultTestSuite -Dandroidx.test.backup.device.serial.source=emulator-5554 -Dandroidx.test.backup.device.serial.target=emulator-5556

Uma vinculação se aplica apenas a parâmetros com esse papel. Outros parâmetros recebem os dispositivos correspondentes restantes.

Também é possível filtrar dispositivos com serial, api, minApi e maxApi em @Device, como @Device(role = "target", minApi = 35). Para definir números de série ou níveis da API exatos na linha de comando, transmita as seguintes propriedades do sistema com -D:

Propriedade do sistema Descrição
androidx.test.backup.device.serial.{role} O número de série do dispositivo para parâmetros anotados com @Device(role = "{role}"). Tem precedência sobre serials, serial e o valor serial de @Device.
androidx.test.backup.device.serials Uma lista separada por vírgulas de números de série, atribuídos aos parâmetros do dispositivo em ordem. Tem precedência sobre serial e o valor serial de @Device.
androidx.test.backup.device.serial O número de série dos parâmetros do dispositivo que não são cobertos por serials. Tem precedência sobre o valor serial de @Device.
androidx.test.backup.device.apis Uma lista separada por vírgulas de níveis da API, atribuídos aos parâmetros do dispositivo em ordem. Use 0 para corresponder a qualquer nível da API. Substitui api em @Device, dentro de minApi e maxApi.
androidx.test.backup.device.api O nível da API para parâmetros de dispositivo que não são cobertos por apis. Use 0 para corresponder a qualquer nível da API. Substitui api em @Device, dentro de minApi e maxApi.

As propriedades serial, serials, api e apis se aplicam a todos os testes executados. Portanto, use a opção --tests para executar apenas os testes que precisam delas.

Testar as credenciais de restauração

Se o app usa o recurso Restaurar credenciais, teste se a chave de restauração sobrevive a um backup e restauração. Os modos de nuvem e de dispositivo para dispositivo fazem backup da chave com os dados do app, mas o LOCAL não.

Escreva ações do dispositivo que criem a chave e a recebam com o Credential Manager. A ação de criação retorna o ID da credencial da chave para que a ação de verificação possa confirmar que a restauração traz de volta a mesma chave. Neste exemplo, createRequestJson e getRequestJson são as strings JSON de solicitação que o app transmite ao Gerenciador de credenciais:

// 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")
        }
    }
}

O teste de host a seguir cria a chave no dispositivo de origem, a restaura no dispositivo de destino e verifica se o ID da credencial corresponde:

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 criar uma chave com isCloudBackupEnabled definido como true, o padrão, o dispositivo de origem precisa de uma Conta do Google com o backup ativado e um bloqueio de tela. Caso contrário, o Credential Manager vai gerar E2eeUnavailableException.

Ao contrário de uma restauração na nuvem em um novo dispositivo, os modos de nuvem não exigem a mesma Conta do Google ou o bloqueio de tela do dispositivo de origem e restauram as chaves criadas com isCloudBackupEnabled definido como false.

Manter os dados do app entre os testes

Antes de cada teste, o framework limpa os dados do app em cada dispositivo usado. Para desativar esse recurso em uma classe ou método de teste, adicione a anotação @Isolation(IsolationPolicy.MANUAL).

Criar testes em Java

Os métodos BackupRestoreController são funções de suspensão do Kotlin. Se você escrever testes de host em Java, use a variante Async de cada método, como runBackupRestoreFlowAsync, que retorna um ListenableFuture.

Executar testes de backup e restauração

O AGP cria uma tarefa do Gradle para cada variante testada pelo pacote, chamada test{Variant}{SuiteName}DefaultTestSuite. Para executar o pacote backupTest na variante debug, conecte os dispositivos e execute a tarefa:

./gradlew :app:testDebugBackupTestDefaultTestSuite

As tarefas check, test e connectedCheck não executam pacotes de teste de backup e restauração. Portanto, execute a tarefa do pacote explicitamente.

Para executar uma única classe ou método de teste, use a opção --tests:

./gradlew :app:testDebugBackupTestDefaultTestSuite --tests "com.example.myapp.ThemeBackupTest"

Inspecionar relatórios de teste

Depois que uma execução de teste é concluída, o AGP gera um relatório de teste em HTML em {module}/build/reports/tests/{taskName}/index.html, como app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html.

Quando um teste falha, o framework de teste salva o logcat de cada dispositivo usado pelo teste e lista os caminhos de arquivo salvos na saída de falha. Para desativar o salvamento de registros, transmita -Dandroidx.test.backup.device.logs.enabled=false.

Resolver problemas de restaurações com falha

Uma restauração na nuvem ou de dispositivo para dispositivo falha com uma mensagem de erro nos seguintes casos:

  • O Google Play Services ignora seu app, por exemplo, porque o ID do aplicativo começa com com.google. ou com.android., o backup está vazio ou define android:allowBackup="false". Para testar um app com esse ID, use o modo LOCAL.
  • A versão do Google Play Services é anterior à 26.33.30 em um dispositivo ou emulador que executa o nível 36.1 ou mais recente da API. Atualize o Google Play Services pela Play Store no dispositivo ou use uma imagem do sistema que venha com a versão 26.33.30 ou mais recente.

Limitações e problemas conhecidos

O teste automatizado de backup e restauração tem os seguintes problemas e limitações conhecidos:

  • O suporte do Android Studio para pacotes de testes ainda não está disponível. O editor de código pode mostrar erros de referência não resolvidos em arquivos de origem do pacote de testes, mesmo quando o projeto está configurado corretamente. Crie e execute testes de backup e restauração na linha de comando usando o Gradle.
  • Os testes são executados apenas em dispositivos e emuladores conectados à sua máquina de desenvolvimento pelo adb. O Firebase Test Lab não é compatível.
  • Os testes limpam os dados do app e mudam as configurações de backup nos dispositivos usados. Recomendamos o uso de dispositivos de teste dedicados.

Para ver as mudanças em cada versão, consulte as notas da versão de teste de backup e restauração.