Otomatik yedekleme ve geri yükleme testi

Kullanıcılar yeni bir cihaz kurduğunda veya uygulamanızı yeniden yüklediğinde Android, uygulamanızın verilerini bulut yedeğinden ya da cihazlar arası aktarımla geri yükleyebilir. Yedekleme kurallarınız önemli verileri dışarıda bırakıyorsa veya uygulamanız geri yüklediği verileri kullanamıyorsa kullanıcılar hata görmeden ayarlarını ve ilerleme durumlarını kaybeder.

Otomatik yedekleme ve geri yükleme testleri, uygulamanızı yayınlamadan önce bu sorunları tespit etmenize yardımcı olur. Bu testler, geliştirme makinenizde çalışan ve bağlı cihazları Android Debug Bridge (adb) aracılığıyla kontrol eden JUnit 5 testleridir. Her test, cihazdaki uygulama verilerini başlatır, Android yedekleme çerçevesi aracılığıyla uygulamayı yedekleyip geri yükler ve ardından verilerin bozulmadan kaldığını kontrol eder. Testleri, modülünüzün derleme dosyasında Android Gradle eklentisi (AGP) test paketi olarak yapılandırırsınız.

İşleyiş şekli

Yedekleme ve geri yükleme test paketinin birlikte çalışan iki bölümü vardır:

  • Ana makine testleri, geliştirme makinenizde çalışan JUnit 5 testleridir. Verileri beslemek, uygulamanızı yedekleyip geri yüklemek ve sonuçları kontrol etmek için her cihazda bir BackupRestoreController kullanırlar.
  • Cihaz işlemleri, BackupDeviceAction sınıfını uygulayan sınıflardır. Cihazdaki uygulamanızın sürecinde çalıştıkları için uygulamanızın kendi kodu aracılığıyla verileri okuyup yazabilirler. Ana makine, sınıf adına göre cihaz işlemlerini test eder.

Paketi çalıştırdığınızda AGP, uygulamanızı ve cihaz işlemlerinizi içeren bir test APK'sı oluşturur. Test çerçevesi, her testten önce her iki APK'yı da testin kullandığı her cihaza yükler ve ardından uygulamanızın verilerini temizler.

Yerleşik depolama türleri

Aşağıdaki tabloda, cihaz işlemleri yazmadan veri eklemenize ve doğrulama verisi doğrulamanıza olanak tanıyan StorageDomain alt sınıfları listelenmiştir:

Depolama alanı Veriler
StorageDomain.Preference SharedPreferences dosyasındaki bir anahtar.
StorageDomain.Database SQLite veritabanı tablosundaki bir satır.
StorageDomain.TextFile Uygulamanızın dahili depolama alanındaki bir metin dosyası.
StorageDomain.BinaryFile Uygulamanızın dahili depolama alanındaki bir ikili program dosyası.

DataStore dosyaları gibi diğer verileri test etmek için cihaz işlemleri yazın.

Ulaşım modları

Her yedekleme için bir BackupTransportMode değeri kullanılır. Aşağıdaki tabloda desteklenen ulaşım modları listelenmiştir:

Mod Senaryo
LOCAL Cihazın yerel yedekleme aktarımına yedekleme. Yedekleme verileri cihazda kalır.
CLOUD_ENCRYPTED İstemci tarafı şifreleme özelliğine sahip bir cihazdan kullanıcının Google Hesabı'na kaydedilen bulut yedeklemesi gibi bir yedekleme.
CLOUD_UNENCRYPTED İstemci tarafı şifreleme özelliği olmayan bir cihazdan alınan bulut yedeği.
DEVICE_TO_DEVICE Cihazdan cihaza aktarım (ör. kablo veya kablosuz ağ üzerinden)

LOCAL desteklenen tüm cihazlarda çalışır. Bulut ve cihazlar arası modlar, Android Studio'daki Uygulama Verilerini Yedekle işlemiyle aynı yedekleri oluşturur: Yedek, başka bir cihaza yüklenmek veya gönderilmek yerine geliştirme makinenize kaydedilir. Bulut ve cihazlar arası modlar, cihazda Google Play Hizmetleri'nin ve uygulamanızın hata ayıklanabilir bir derlemesinin olmasını gerektirir. Yedekleme kurallarınız istemci tarafı şifrelemeye bağlıysa her iki bulut modunu da test edin.

Şartlar

Otomatik yedekleme ve geri yükleme testini kullanmak için ortamınızın aşağıdaki koşulları karşıladığından emin olun:

  • Gradle 9.6.0 veya sonraki bir sürüm.
  • JDK 17 veya sonraki sürümler.
  • Android 12 (API düzeyi 31) veya sonraki sürümlerin yüklü olduğu ve adb üzerinden bağlanan bir cihaz ya da emülatör. İki cihaz arasındaki aktarımları test etmek için iki cihaz bağlayın.

Uyumluluk

Aşağıdaki tabloda, otomatik yedekleme ve geri yükleme testi için minimum ve önerilen sürümler listelenmiştir:

Bileşen Minimum sürüm Önerilen sürüm
Android Studio Rabbit 2 Canary 1 Rabbit 2 Canary 4 veya daha yeni sürümler
Android Gradle eklentisi (AGP) 9.5.0-alpha06 9.5.0-alpha09 veya daha yeni bir sürüm
androidx.test.backup kitaplık 1.0.0-alpha01 1.0.0-alpha02 veya daha yeni bir sürüm

AGP'nin her önizleme sürümü, Android Studio'nun eşleşen önizleme sürümünü gerektirir. Daha fazla bilgi için AGP önizlemeleri başlıklı makaleyi inceleyin.

Projenizi oluşturma

Uygulama modülünüze yedekleme ve geri yükleme test paketi eklemek için aşağıdaki adımları tamamlayın:

Deneysel işaretleri etkinleştirme

Projenizin gradle.properties dosyasında test paketlerini ve yedekleme testlerini etkinleştirin:

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

Test paketini tanımlama

Uygulama modülünüzün build.gradle.kts dosyasında, testOptions bloğunda bir yedekleme test paketi tanımlayın:

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

Bu örnekte backupTest olan paket adı, kaynak grubu dizinlerini ve Gradle görev adlarını belirler. Paketi aşağıdaki isteğe bağlı özelliklerle özelleştirebilirsiniz:

  • AGP'nin pakete eklediği androidx.test.backup kitaplıklarının sürümünü (ör. "1.0.0-alpha02") ayarlamak için backupTestLibraryVersion kullanın. Bu özellik isteğe bağlıdır. Bu bağımlılığı atlarsanız AGP, AGP sürümüne göre varsayılan bir kitaplık sürümüne geri döner.
  • Paketin test ettiği derleme değişkenlerini (ör. targetVariants.add("demoDebug")) belirtmek için targetVariants kullanın. Bu özellik isteğe bağlıdır. Bu parametreyi atlarsanız paket, modülünüzün testBuildType varyantlarını test eder. Bu varyantlar varsayılan olarak debug şeklindedir. Bulut ve cihazlar arası aktarım modları hata ayıklanabilir bir uygulama gerektirdiğinden hata ayıklanabilir bir varyantı test etmenizi öneririz.

AGP, ana makine testlerine ana makine kitaplığını, androidx.test.backup:backup-host ve JUnit 5'i ekler. Test APK'sına cihaz kitaplığını androidx.test.backup:backup ekler ve test APK'sının enstrümantasyon çalıştırıcısını ayarlar.

Başka bağımlılıklar eklemek için paketin hostJar ve testApk bloklarını kullanın. Örneğin, ana makine testlerinizde Truth'u kullanmak için hostJar bloğuna ekleyin:

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

Kaynak gruplarını oluşturma

Paket adıyla eşleşen kaynak grubu dizinleri oluşturun:

  • Ana makine testlerini src/{suiteName}/test/kotlin/ dizinine yerleştirin.
  • Cihaz işlemlerini src/{suiteName}/androidTest/kotlin/ içine yerleştirin.

Örneğin, backupTest adlı bir pakette aşağıdaki dizin yapısı kullanılır:

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

Yedekleme yapılandırmanızı kontrol etme

Testler, uygulamanızın yedekleme yapılandırmasının içerdiği verileri yedekler ve geri yükler. Uygulamanız için yedeklemenin etkinleştirildiğinden ve yedekleme kurallarınızın test ettiğiniz verileri içerdiğinden emin olun.

Yedekleme ve geri yükleme testleri yazma

Paketin test kaynak grubunda ana makine testleri yazın. Her test sınıfının iki ek açıklaması olması gerekir:

  • @ExtendWith(BackupRestoreExtension::class), testlere bağlı cihazlar sağlar.
  • @BackupRestoreConfig(applicationId = "..."), test edilen uygulamanın uygulama kimliğini ayarlar. Test ettiğiniz varyantın uygulama kimliğini kullanın. applicationIdSuffix değerini de ekleyin.

Her test yöntemi, kullandığı her cihaz için bir BackupRestoreController parametresi tanımlar.

Yerleşik depolama alanı türlerini test etme

Yerleşik depolama alanı türlerini test etmek için temel veriler, yedek dosyasının dizini ve ulaşım modu ile runBackupRestoreFlow yöntemini çağırın. Yöntem, cihazda aşağıdaki adımları uygular:

  1. Her depolama alanını başlatır.
  2. Uygulamayı durdurur.
  3. Uygulamayı yedekler.
  4. Uygulamanın verilerini temizler.
  5. Yedeği geri yükler.
  6. Her depolama alanının çekirdek değere sahip olduğunu doğrular.

Bir adım başarısız olursa yöntem bir istisna hatası verir ve bu da testin başarısız olmasına neden olur. Aşağıdaki örnekte, yerel yedekleme aktarımı kullanılarak bir SharedPreferences anahtarı, bir SQLite satırı ve dahili bir dosya test edilmektedir:

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

Depolama alanlarını tanımlarken aşağıdakileri göz önünde bulundurun:

  • Preference, .xml uzantısı olmadan SharedPreferences dosyasının adını alır.
  • TextFile ve BinaryFile, göreli yolları uygulamanızın filesDir dizinine göre çözümler.
  • Database, satırı SQL ile yazar ve tablo yoksa TEXT sütunlu tabloyu oluşturur. Uygulamanızda Room kullanılıyorsa veritabanının Room'un beklediği şemaya sahip olması için veritabanı içeriğini DAO'larınızı kullanan cihaz işlemleriyle test etmenizi öneririz.

Cihaz işlemlerini yazma

Yerleşik depolama alanı türlerinin kapsamadığı verileri eklemek veya doğrulamak için paketin androidTest kaynak grubuna cihaz işlemleri yazın. Bir cihaz işlemi, BackupDeviceAction öğesini uygular ve iki üyeyi geçersiz kılar:

  • Veri ekleyen bir işlem için phase değerini BackupDeviceAction.PHASE_POPULATE, verileri kontrol eden bir işlem için ise BackupDeviceAction.PHASE_VERIFY olarak ayarlayın.
  • execute, uygulamanızın Context ve ana makine testindeki bağımsız değişkenlerle işlemi çalıştırmak için uygulanır. BackupDeviceActionResult.success veya BackupDeviceActionResult.failure fabrika yöntemiyle oluşturulmuş bir sonuç döndürün ve failure yöntemine bir hata mesajı iletin.

execute yöntemi, uygulamanızın sürecinde arka plan iş parçacığında çalışır. Bu nedenle, okuma ve yazma işlemleri tamamlanana kadar engellenebilir.

Aşağıdaki örnek, uygulamanın Preferences DataStore ile depoladığı bir tema ayarını başlatır ve doğrular. Uygulama, DataStore'u ana kaynak grubunda tanımlar:

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

Cihaz işlemleri aynı tanımları kullanır:

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

Verileri ana makine testine döndürmek için success yöntemine bir harita iletin. Ana makine testi, haritayı BackupActionResult.Success.data konumundan okur.

Akışın her adımını çalıştırma

Cihaz işlemlerini kullanmak veya akışı özelleştirmek için her adımı kendiniz çağırın. Aşağıdaki ana makine testi, önceki bölümdeki cihaz işlemlerini bulut yedekleme ve geri yükleme ile ilgili olarak çalıştırır:

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

Bir cihaz işlemi başarısız olduğunda runOnDevice, istisna oluşturmak yerine BackupActionResult.Failure döndürür. Bu nedenle sonucu kontrol edin. Diğer adımlar başarısız olursa istisna oluşturur.

Cihazlar arasında aktarım testi yapma

İki cihaz arasında aktarımı test etmek için her cihaz için bir BackupRestoreController parametresi bildirin. Uygulamayı kaynak cihazda yedekleyin ve ardından yedek dosyasını hedef cihaza geri yükleyin. Aşağıdaki örnekte, önceki örnekteki sabitler ve assertSuccess yardımcı işlevi yeniden kullanılmaktadır:

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

@Device için role değeri, testin daha kolay okunmasını sağlayan bir etikettir. Bu ayar, bir parametrenin hangi cihazı alacağını etkilemez. Yalnızca bir cihaz bağlıysa her iki parametre de bu cihazı kullanır ve test, tek bir cihazda aktarımı simüle eder. Hedefin verilerini temizlemek, geri yüklenen verilerin yedekten gelmesini sağlar.

Cihazları seçin

Test çerçevesi, varsayılan olarak çevrimiçi cihazları seri numarasına göre sıralar ve konumlarına göre BackupRestoreController parametrelerine atar. Örneğin, test yönteminin ilk parametresi ilk cihazı, ikinci parametresi ise ikinci cihazı alır. Bir konum için cihaz yoksa parametre ilk cihazı alır. Konumlar, yöntemin tüm parametrelerini sayar. Bu nedenle, cihaz parametrelerini diğer parametrelerden (ör. @TempDir) önce tanımlayın. ANDROID_SERIAL ortam değişkeni, cihaz atamasını etkilemez.

Cihazları seçmek için @Device'nin serial veya api değerlerini ya da JVM sistem özelliklerini ayarlayın. Sistem özellikleri, @Device değerlerine göre önceliklidir. Aşağıdaki tabloda desteklenen sistem özellikleri listelenmiştir:

Sistem özelliği Açıklama
androidx.test.backup.device.serial Tüm cihaz parametreleri için cihazın seri numarası.
androidx.test.backup.device.serials Parametrelere konuma göre atanan, virgülle ayrılmış seri numaraları listesi.
androidx.test.backup.device.serial.{name} @Device(serial = "{name}") ile ek açıklama eklenen parametrelerin seri numarası.
androidx.test.backup.device.api Tüm cihaz parametreleri için cihazın API düzeyi.
androidx.test.backup.device.apis Konuma göre parametrelere atanmış API düzeylerinin virgülle ayrılmış listesi.

Testleri komut satırından çalıştırırken bu özellikleri -D ile iletin. Özellikler, çalışan her test için geçerlidir. Bu nedenle, yalnızca bu özelliklere ihtiyaç duyan testleri çalıştırmak için --tests seçeneğini kullanın. Örneğin, aşağıdaki komut ThemeTransferTest uygulamasını kaynak cihaz olarak emulator-5554, hedef cihaz olarak da emulator-5556 ile çalıştırır:

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

Testler arasında uygulama verilerini saklama

Test çerçevesi, her testten önce testin kullandığı her cihazda uygulamanızın verilerini temizler. Bu özelliği bir test sınıfı veya yöntemi için devre dışı bırakmak üzere @Isolation(IsolationPolicy.MANUAL) ile açıklama ekleyin.

Java'da test yazma

BackupRestoreController yöntemleri Kotlin askıya alma işlevleridir. Java'da ana makine testleri yazıyorsanız her yöntemin Async varyantını (ör. runBackupRestoreFlowAsync) kullanın. Bu varyant, ListenableFuture döndürür.

Yedekleme ve geri yükleme testleri yapma

AGP, paketin test ettiği her varyant için test{Variant}{SuiteName}DefaultTestSuite adlı bir Gradle görevi oluşturur. backupTest paketini debug varyantında çalıştırmak için cihazlarınızı bağlayın ve şu görevi çalıştırın:

./gradlew :app:testDebugBackupTestDefaultTestSuite

check, test ve connectedCheck görevleri, yedekleme ve geri yükleme test paketlerini çalıştırmaz. Bu nedenle, paketin görevini açıkça çalıştırın.

Tek bir test sınıfı veya yöntemi çalıştırmak için --tests seçeneğini kullanın:

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

Test raporlarını inceleme

Test çalıştırması tamamlandıktan sonra AGP, {module}/build/reports/tests/{taskName}/index.html konumunda bir HTML test raporu oluşturur. Örneğin, app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html.

Bilinen sorunlar ve sınırlamalar

Otomatik yedekleme ve geri yükleme testinde aşağıdaki bilinen sorunlar ve sınırlamalar vardır:

  • Test paketleri için Android Studio desteği henüz kullanılamamaktadır. Kod düzenleyici, projeniz doğru şekilde yapılandırılmış olsa bile test paketi kaynak dosyalarında çözümlenmemiş referans hataları gösterebilir. Bunun yerine Gradle'ı kullanarak komut satırından yedekleme ve geri yükleme testleri oluşturup çalıştırın.
  • Testler yalnızca adb aracılığıyla geliştirme makinenize bağlı cihazlarda ve emülatörlerde çalıştırılır. Firebase Test Lab desteklenmiyor.
  • Testler, uygulamanızın verilerini temizler ve kullandıkları cihazlardaki yedekleme ayarlarını değiştirir. Özel test cihazları kullanmanızı öneririz.

Her sürümdeki değişiklikler için yedekleme ve geri yükleme testi sürüm notlarına bakın.