Pengujian pencadangan dan pemulihan otomatis

Saat pengguna menyiapkan perangkat baru atau menginstal ulang aplikasi Anda, Android dapat memulihkan data aplikasi Anda dari pencadangan cloud atau transfer antarperangkat. Jika aturan pencadangan Anda mengabaikan data penting, atau aplikasi Anda tidak dapat menggunakan data yang dipulihkan, pengguna akan kehilangan setelan dan progresnya tanpa melihat error.

Pengujian pencadangan dan pemulihan otomatis membantu Anda mendeteksi masalah ini sebelum Anda merilis aplikasi. Pengujian ini adalah pengujian JUnit 5 yang berjalan di mesin pengembangan dan mengontrol perangkat terhubung melalui Android Debug Bridge (adb). Setiap pengujian mengisi data aplikasi di perangkat, mencadangkan dan memulihkan aplikasi melalui framework pencadangan Android, lalu memeriksa apakah data masih utuh. Anda mengonfigurasi pengujian sebagai suite pengujian plugin Android Gradle (AGP) di file build modul Anda.

Cara kerjanya

Suite pengujian pencadangan dan pemulihan memiliki dua bagian yang bekerja sama:

  • Pengujian host adalah pengujian JUnit 5 yang berjalan di mesin pengembangan Anda. Mereka menggunakan BackupRestoreController untuk setiap perangkat guna mengisi data, mencadangkan dan memulihkan aplikasi Anda, serta memeriksa hasilnya.
  • Tindakan perangkat adalah class yang menerapkan BackupDeviceAction. Pengujian ini berjalan dalam proses aplikasi Anda di perangkat, sehingga dapat membaca dan menulis data melalui kode aplikasi Anda sendiri. Pengujian Host memanggil tindakan perangkat berdasarkan nama class.

Saat Anda menjalankan rangkaian pengujian, AGP akan mem-build aplikasi dan APK pengujian yang berisi tindakan perangkat Anda. Sebelum setiap pengujian, framework pengujian menginstal kedua APK di setiap perangkat yang digunakan pengujian, lalu menghapus data aplikasi Anda.

Jenis penyimpanan bawaan

Tabel berikut mencantumkan subclass StorageDomain yang memungkinkan Anda menyisipkan dan memverifikasi data tanpa menulis tindakan perangkat:

Domain penyimpanan Data
StorageDomain.Preference Kunci dalam file SharedPreferences.
StorageDomain.Database Baris dalam tabel database SQLite.
StorageDomain.TextFile File teks di penyimpanan internal aplikasi Anda.
StorageDomain.BinaryFile File biner di penyimpanan internal aplikasi Anda.

Untuk menguji data lain, seperti file DataStore, tulis tindakan perangkat.

Mode transportasi

Setiap cadangan menggunakan nilai BackupTransportMode. Tabel berikut mencantumkan mode transportasi yang didukung:

Mode Skenario
LOCAL Pencadangan ke transportasi pencadangan lokal perangkat. Data cadangan tetap berada di perangkat.
CLOUD_ENCRYPTED Cadangan cloud, seperti yang disimpan ke Akun Google pengguna dari perangkat dengan enkripsi sisi klien.
CLOUD_UNENCRYPTED Cadangan cloud dari perangkat yang tidak memiliki enkripsi sisi klien.
DEVICE_TO_DEVICE Transfer antarperangkat, misalnya melalui kabel atau Wi-Fi.

LOCAL berfungsi di perangkat yang didukung. Mode cloud dan perangkat-ke-perangkat membuat cadangan yang sama seperti tindakan Backup data aplikasi di Android Studio: cadangan disimpan ke mesin pengembangan Anda, bukan diupload atau dikirim ke perangkat lain. Mode cloud dan perangkat ke perangkat memerlukan layanan Google Play di perangkat dan build aplikasi yang dapat di-debug. Jika aturan pencadangan Anda bergantung pada enkripsi sisi klien, uji kedua mode cloud.

Persyaratan

Untuk menggunakan pengujian pencadangan dan pemulihan otomatis, pastikan lingkungan Anda memenuhi persyaratan berikut:

  • Gradle versi 9.6.0 atau yang lebih tinggi.
  • JDK versi 17 atau yang lebih baru.
  • Perangkat atau emulator yang menjalankan Android 12 (level API 31) atau yang lebih tinggi yang terhubung melalui adb. Untuk menguji transfer antara dua perangkat, hubungkan dua perangkat.

Kompatibilitas

Tabel berikut mencantumkan versi minimum dan yang direkomendasikan untuk pengujian pencadangan dan pemulihan otomatis:

Komponen Versi minimum Versi yang direkomendasikan
Android Studio Rabbit 2 Canary 1 Rabbit 2 Canary 4 atau yang lebih tinggi
Plugin Android Gradle (AGP) 9.5.0-alpha06 9.5.0-alpha09 atau yang lebih tinggi
androidx.test.backup library 1.0.0-alpha01 1.0.0-alpha02 atau yang lebih tinggi

Setiap versi pratinjau AGP memerlukan versi pratinjau Android Studio yang cocok. Untuk mengetahui informasi selengkapnya, lihat pratinjau AGP.

Menyiapkan project

Untuk menambahkan suite pengujian pencadangan dan pemulihan ke modul aplikasi, selesaikan langkah-langkah berikut:

Mengaktifkan tanda eksperimental

Dalam file gradle.properties project Anda, aktifkan suite pengujian dan pengujian cadangan:

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

Mendeklarasikan suite pengujian

Dalam file build.gradle.kts modul aplikasi, deklarasikan suite pengujian pencadangan di blok testOptions:

android {
    testOptions {
        backupTests {
            create("backupTest")
        }
    }
}

Nama rangkaian pengujian, backupTest dalam contoh ini, menentukan direktori set sumber dan nama tugas Gradle. Anda dapat menyesuaikan suite dengan properti opsional berikut:

  • Gunakan backupTestLibraryVersion untuk menetapkan versi library androidx.test.backup yang ditambahkan AGP ke suite, seperti "1.0.0-alpha02". Properti ini bersifat opsional. Jika Anda menghilangkannya, AGP akan melakukan penggantian ke versi library default berdasarkan versi AGP.
  • Gunakan targetVariants untuk menentukan varian build yang diuji suite, seperti targetVariants.add("demoDebug"). Properti ini bersifat opsional. Jika Anda menghilangkannya, suite akan menguji varian testBuildType modul Anda, yang secara default adalah debug. Sebaiknya uji varian yang dapat di-debug karena mode transportasi cloud dan perangkat ke perangkat memerlukan aplikasi yang dapat di-debug.

AGP menambahkan library host, androidx.test.backup:backup-host, dan JUnit 5 ke pengujian host. Tindakan ini akan menambahkan library perangkat, androidx.test.backup:backup, ke APK pengujian dan menetapkan runner instrumentasi APK pengujian.

Untuk menambahkan dependensi lain, gunakan blok hostJar dan testApk dari suite. Misalnya, untuk menggunakan Truth dalam pengujian host, tambahkan ke blok hostJar:

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

Membuat set sumber

Buat direktori set sumber yang cocok dengan nama rangkaian pengujian:

  • Letakkan pengujian host di src/{suiteName}/test/kotlin/.
  • Tempatkan tindakan perangkat di src/{suiteName}/androidTest/kotlin/.

Misalnya, rangkaian pengujian bernama backupTest menggunakan struktur direktori berikut:

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

Memeriksa konfigurasi cadangan Anda

Pengujian mencadangkan dan memulihkan data yang disertakan dalam konfigurasi pencadangan aplikasi Anda. Pastikan pencadangan diaktifkan untuk aplikasi Anda dan aturan pencadangan menyertakan data yang Anda uji.

Menulis pengujian pencadangan dan pemulihan

Tulis pengujian host di set sumber test suite. Setiap class pengujian memerlukan dua anotasi:

  • @ExtendWith(BackupRestoreExtension::class) menyediakan perangkat terhubung ke pengujian.
  • @BackupRestoreConfig(applicationId = "...") menetapkan ID aplikasi aplikasi yang sedang diuji. Gunakan ID aplikasi varian yang Anda uji, termasuk applicationIdSuffix.

Setiap metode pengujian mendeklarasikan parameter BackupRestoreController untuk setiap perangkat yang digunakannya.

Menguji jenis penyimpanan bawaan

Untuk menguji jenis penyimpanan bawaan, panggil runBackupRestoreFlow dengan data yang akan di-seed, direktori untuk file cadangan, dan mode transpor. Metode menjalankan langkah-langkah berikut di perangkat:

  1. Menyemai setiap domain penyimpanan.
  2. Menghentikan aplikasi.
  3. Mencadangkan aplikasi.
  4. Menghapus data aplikasi.
  5. Memulihkan cadangan.
  6. Memverifikasi bahwa setiap domain penyimpanan memiliki nilai awal.

Jika langkah gagal, metode akan menampilkan pengecualian, yang menyebabkan pengujian gagal. Contoh berikut menguji kunci SharedPreferences, baris SQLite, dan file internal menggunakan transportasi cadangan lokal:

package com.example.myapp

import androidx.test.backup.host.BackupRestoreConfig
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)
@BackupRestoreConfig(applicationId = "com.example.myapp")
class SettingsBackupTest {

    @Test
    fun settingsSurviveBackupAndRestore(
        device: BackupRestoreController,
        @TempDir outputDir: Path,
    ): Unit = runBlocking {
        device.runBackupRestoreFlow(
            storages = listOf(
                StorageDomain.Preference(
                    prefName = "settings",
                    key = "theme",
                    value = "dark",
                ),
                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",
                ),
            ),
            outputDir = outputDir,
            mode = BackupTransportMode.LOCAL,
        )
    }
}

Perhatikan hal-hal berikut saat Anda menentukan domain penyimpanan:

  • Preference mengambil nama file SharedPreferences tanpa ekstensi .xml.
  • TextFile dan BinaryFile menyelesaikan jalur relatif terhadap filesDir aplikasi Anda.
  • Database menulis baris dengan SQL, dan membuat tabel dengan kolom TEXT jika tabel tidak ada. Jika aplikasi Anda menggunakan Room, sebaiknya uji konten database dengan tindakan perangkat yang menggunakan DAO Anda sehingga database memiliki skema yang diharapkan Room.

Menulis tindakan perangkat

Untuk menyisipkan atau memverifikasi data yang tidak tercakup dalam jenis penyimpanan bawaan, tulis tindakan perangkat di set sumber androidTest suite. Tindakan perangkat mengimplementasikan BackupDeviceAction dan menggantikan dua anggota:

  • Tetapkan phase ke BackupDeviceAction.PHASE_POPULATE untuk tindakan yang menyemai data, atau BackupDeviceAction.PHASE_VERIFY untuk tindakan yang memeriksa data.
  • Terapkan execute untuk menjalankan tindakan dengan Context aplikasi Anda dan argumen dari pengujian host. Menampilkan hasil yang dibuat dengan metode factory BackupDeviceActionResult.success atau BackupDeviceActionResult.failure, dan meneruskan pesan error ke failure.

Metode execute berjalan dalam proses aplikasi Anda di thread latar belakang, sehingga dapat memblokir hingga operasi baca dan tulis selesai.

Contoh berikut menyemai dan memverifikasi setelan tema yang disimpan aplikasi dengan Preferences DataStore. Aplikasi menentukan DataStore di set sumber utamanya:

// In app/src/main/kotlin/com/example/myapp/Settings.kt
package com.example.myapp

import android.content.Context
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore

val Context.settingsDataStore by preferencesDataStore(name = "settings")
val THEME_KEY = stringPreferencesKey("theme")

Tindakan perangkat menggunakan definisi yang sama:

// In app/src/backupTest/androidTest/kotlin/com/example/myapp/ThemeActions.kt
package com.example.myapp

import android.content.Context
import androidx.datastore.preferences.core.edit
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 val phase = BackupDeviceAction.PHASE_POPULATE

    override fun execute(
        context: Context,
        args: BackupDeviceActionArgs,
    ): BackupDeviceActionResult {
        val theme = args["theme"]
            ?: return BackupDeviceActionResult.failure("Missing theme argument")
        runBlocking {
            context.settingsDataStore.edit { it[THEME_KEY] = theme }
        }
        return BackupDeviceActionResult.success()
    }
}

class VerifyThemeAction : BackupDeviceAction {
    override val phase = BackupDeviceAction.PHASE_VERIFY

    override fun execute(
        context: Context,
        args: BackupDeviceActionArgs,
    ): BackupDeviceActionResult {
        val expected = args["theme"]
        val actual = runBlocking {
            context.settingsDataStore.data.first()[THEME_KEY]
        }
        return if (actual == expected) {
            BackupDeviceActionResult.success()
        } else {
            BackupDeviceActionResult.failure("Expected $expected, found $actual")
        }
    }
}

Untuk menampilkan data ke pengujian host, teruskan peta ke metode success. Pengujian host membaca peta dari BackupActionResult.Success.data.

Menjalankan setiap langkah alur

Untuk menggunakan tindakan perangkat, atau untuk menyesuaikan alur, panggil setiap langkah sendiri. Pengujian host berikut menjalankan tindakan perangkat dari bagian sebelumnya seputar pencadangan dan pemulihan cloud:

package com.example.myapp

import androidx.test.backup.host.BackupActionResult
import androidx.test.backup.host.BackupRestoreConfig
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.fail
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)
@BackupRestoreConfig(applicationId = "com.example.myapp")
class ThemeBackupTest {

    @Test
    fun themeSurvivesCloudBackup(
        device: BackupRestoreController,
        @TempDir outputDir: Path,
    ): Unit = runBlocking {
        val args = mapOf("theme" to "dark")
        device.runOnDevice(SEED_THEME, args).assertSuccess()

        device.stopApp()
        val backupFile = device.performBackup(
            BackupTransportMode.CLOUD_ENCRYPTED,
            outputDir,
        )
        device.clearAppData()
        device.performRestore(backupFile)

        device.runOnDevice(VERIFY_THEME, args).assertSuccess()
    }
}

fun BackupActionResult.assertSuccess() {
    if (this is BackupActionResult.Failure) fail(errorMessage)
}

Jika tindakan perangkat gagal, runOnDevice akan menampilkan BackupActionResult.Failure, bukan menampilkan pengecualian, jadi periksa hasilnya. Langkah lainnya akan menampilkan pengecualian jika gagal.

Menguji transfer antar-perangkat

Untuk menguji transfer antara dua perangkat, deklarasikan parameter BackupRestoreController untuk setiap perangkat. Cadangkan aplikasi di perangkat sumber, lalu pulihkan file cadangan di perangkat target. Contoh berikut menggunakan kembali konstanta dan helper assertSuccess dari contoh sebelumnya:

package com.example.myapp

import androidx.test.backup.host.BackupRestoreConfig
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 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)
@BackupRestoreConfig(applicationId = "com.example.myapp")
class ThemeTransferTest {

    @Test
    fun themeTransfersToNewDevice(
        @Device(role = "source") source: BackupRestoreController,
        @Device(role = "target") target: BackupRestoreController,
        @TempDir outputDir: Path,
    ): Unit = runBlocking {
        val args = mapOf("theme" to "dark")
        source.runOnDevice(SEED_THEME, args).assertSuccess()
        source.stopApp()
        val backupFile = source.performBackup(
            BackupTransportMode.DEVICE_TO_DEVICE,
            outputDir,
        )

        target.clearAppData()
        target.performRestore(backupFile)
        target.runOnDevice(VERIFY_THEME, args).assertSuccess()
    }
}

Nilai role dari @Device adalah label yang membuat pengujian lebih mudah dibaca. Hal ini tidak memengaruhi perangkat yang mendapatkan parameter. Jika hanya satu perangkat yang terhubung, kedua parameter akan menggunakan perangkat tersebut, dan pengujian akan menyimulasikan transfer pada satu perangkat. Menghapus data target akan memastikan bahwa data yang dipulihkan berasal dari cadangan.

Pilih perangkat

Secara default, framework pengujian mengurutkan perangkat online berdasarkan nomor seri dan menetapkannya ke parameter BackupRestoreController berdasarkan posisi. Misalnya, parameter pertama metode pengujian mendapatkan perangkat pertama, dan parameter kedua mendapatkan perangkat kedua. Jika tidak ada perangkat untuk suatu posisi, parameter akan mendapatkan perangkat pertama. Posisi menghitung semua parameter metode, jadi deklarasikan parameter perangkat sebelum parameter lainnya, seperti @TempDir. Variabel lingkungan ANDROID_SERIAL tidak memengaruhi penetapan perangkat.

Untuk memilih perangkat, tetapkan nilai serial atau api dari @Device, atau tetapkan properti sistem JVM. Properti sistem lebih diutamakan daripada nilai @Device. Tabel berikut mencantumkan properti sistem yang didukung:

Properti sistem Deskripsi
androidx.test.backup.device.serial Nomor seri perangkat untuk semua parameter perangkat.
androidx.test.backup.device.serials Daftar nomor seri yang dipisahkan koma, yang ditetapkan ke parameter berdasarkan posisi.
androidx.test.backup.device.serial.{name} Nomor seri untuk parameter yang dianotasi dengan @Device(serial = "{name}").
androidx.test.backup.device.api Level API perangkat untuk semua parameter perangkat.
androidx.test.backup.device.apis Daftar level API yang dipisahkan koma, yang ditetapkan ke parameter berdasarkan posisi.

Teruskan properti ini dengan -D saat Anda menjalankan pengujian dari command line. Properti berlaku untuk setiap pengujian yang berjalan, jadi gunakan opsi --tests untuk hanya menjalankan pengujian yang memerlukannya. Misalnya, perintah berikut menjalankan ThemeTransferTest dengan emulator-5554 sebagai perangkat sumber dan emulator-5556 sebagai perangkat target:

./gradlew :app:testDebugBackupTestDefaultTestSuite --tests "com.example.myapp.ThemeTransferTest" -Dandroidx.test.backup.device.serials=emulator-5554,emulator-5556

Menyimpan data aplikasi di antara pengujian

Sebelum setiap pengujian, framework pengujian akan menghapus data aplikasi Anda di setiap perangkat yang digunakan pengujian. Untuk menonaktifkan ini untuk class atau metode pengujian, anotasikan dengan @Isolation(IsolationPolicy.MANUAL).

Menulis pengujian di Java

Metode BackupRestoreController adalah fungsi penangguhan Kotlin. Jika Anda menulis pengujian host di Java, gunakan varian Async dari setiap metode, seperti runBackupRestoreFlowAsync, yang menampilkan ListenableFuture.

Menjalankan pengujian pencadangan dan pemulihan

AGP membuat tugas Gradle untuk setiap varian yang diuji rangkaian, yang diberi nama test{Variant}{SuiteName}DefaultTestSuite. Untuk menjalankan suite backupTest terhadap varian debug, hubungkan perangkat Anda dan jalankan tugas:

./gradlew :app:testDebugBackupTestDefaultTestSuite

Tugas check, test, dan connectedCheck tidak menjalankan suite pengujian pencadangan dan pemulihan, jadi jalankan tugas suite secara eksplisit.

Untuk menjalankan satu class atau metode pengujian, gunakan opsi --tests:

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

Memeriksa laporan pengujian

Setelah menjalankan pengujian selesai, AGP akan membuat laporan pengujian HTML di {module}/build/reports/tests/{taskName}/index.html, seperti app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html.

Masalah umum dan batasan

Pengujian pencadangan dan pemulihan otomatis memiliki masalah umum dan batasan berikut:

  • Dukungan Android Studio untuk suite pengujian belum tersedia. Editor kode mungkin menampilkan error referensi yang belum terselesaikan dalam file sumber suite pengujian, meskipun project Anda dikonfigurasi dengan benar. Bangun dan jalankan pengujian pencadangan dan pemulihan dari command line menggunakan Gradle.
  • Pengujian hanya berjalan di perangkat dan emulator yang terhubung ke komputer pengembangan Anda melalui adb. Firebase Test Lab tidak didukung.
  • Pengujian menghapus data aplikasi Anda dan mengubah setelan pencadangan di perangkat yang digunakan. Sebaiknya gunakan perangkat pengujian khusus.

Untuk mengetahui perubahan di setiap rilis, lihat catatan rilis pengujian pencadangan dan pemulihan.