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 yedeklemesinden ya da cihazlar arası aktarımdan 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, bir cihazdaki uygulama verilerini besler, Android yedekleme çerçevesi aracılığıyla uygulamayı yedekleyip geri yükler ve ardından verilerin bozulmadığı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'yı uygulayan sınıflardır. Cihazda uygulamanızın işlemi içinde çalıştıkları için uygulamanızın kendi kodu aracılığıyla veri okuyup yazabilirler. Ana makine testleri, cihaz işlemlerini sınıf adına göre çağırır.

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

Yerleşik depolama alanı 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.DataStorePreference Preferences DataStore 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ı.
StorageDomain.ExcludedData Verileri geri yüklenmemesi gereken başka bir yerleşik depolama alanı (ör. yedekleme kurallarınız tarafından hariç tutulan veriler).

Proto DataStore dosyaları veya Room veritabanları 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 Cihazlar arası 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ümleri çalıştıran ve adb üzerinden bağlı bir cihaz ya da emülatör:
    • API düzeyi 36.1 veya daha yüksek olan cihazlar ya da emülatörler için Google Play Hizmetleri'nin 26.33.30 veya daha yüksek bir sürümü gerekir.
    • İ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-alpha04 veya daha yeni sürümler

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") {
                backupTestLibraryVersion = "1.0.0-alpha04"
            }
        }
    }
}

Bu örnekteki backupTest 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-alpha04") ayarlamak için backupTestLibraryVersion kullanın. Bu özellik isteğe bağlıdır. Bu değeri atlarsanız AGP, AGP sürümüne göre varsayılan bir kitaplık sürümüne geri döner. Bu sayfadaki örnekler için 1.0.0-alpha04 veya sonraki bir sürüm gerekir.
  • 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 değişkenlerini test eder. Bu değişken, 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. Cihaz kitaplığını (androidx.test.backup:backup) test APK'sına ekler ve test APK'sının araç çalıştırma aracı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 paket aşağıdaki dizin yapısını kullanı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ına, testlere bağlı cihazlar sağlayan @ExtendWith(BackupRestoreExtension::class) ile ek açıklama ekleyin. Testler, paketin hedeflediği uygulama varyantında çalıştırılır. Bu nedenle, uygulama kimliğini ayarlamanız gerekmez.

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

Yerleşik depolama türlerini test etme

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

  1. Her depolama alanını besler.
  2. Uygulamayı durdurur.
  3. Uygulamayı yedekler.
  4. Uygulamanın verilerini temizler.
  5. Yedeği geri yükler.
  6. Her depolama alanının, StorageDomain.ExcludedData ile sarmalandığında çekirdek değere sahip olduğunu veya mevcut olmadığını doğrular.

Bir adım başarısız olursa yöntem bir istisna hatası verir ve test başarısız olur. Aşağıdaki örnekte yerel yedekleme aktarımı kullanılarak SharedPreferences, Preferences DataStore, SQLite, dahili bir dosya ve hariç tutulacaklar dosyası test edilmektedir:

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

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

  • Preference, SharedPreferences dosyasının adını .xml uzantısı olmadan alır.
  • DataStorePreference, preferencesDataStore parametresine iletilen adı alır. Uygulamanızın geri yüklenen girişi okuyabilmesi için value için uygulamanızın tercih anahtarıyla aynı Kotlin türünü (ör. floatPreferencesKey için 1.25f) kullanın.
  • TextFile ve BinaryFile, uygulamanızın filesDir dizinine göre göreli yolları çözümler.
  • Database, satırı SQL ile yazar ve tablo mevcut değilse TEXT sütunlu tabloyu oluşturur. Uygulamanızda Room kullanılıyorsa veritabanı içeriğini, DAO'larınızı kullanan cihaz işlemleri ile test edin. Böylece veritabanı, Room'un şemasıyla eşleşir.
  • ExcludedData, aynı runBackupRestoreFlow çağrısında hariç tutulmayan en az bir depolama alanı gerektirir.
  • Preference, DataStorePreference, Database, TextFile ve BinaryFile, isDeviceProtectedStorage = true ayarını yaptığınızda uygulamanızın varsayılan depolama alanı yerine cihaz korumalı depolama alanını hedef alır.

Cihaz işlemlerini yazma

Yerleşik depolama türlerinin kapsamadığı verileri eklemek veya doğrulamak için paketin androidTest kaynak grubuna cihaz işlemleri yazın. İşlemi uygulamanızın Context ve ana makine testindeki bağımsız değişkenlerle çalıştırmak için BackupDeviceAction uygulayın ve execute geçersiz kılın. BackupDeviceActionResult.success veya BackupDeviceActionResult.failure ile oluşturulan bir BackupDeviceActionResult döndürün ve failure öğesine bir hata mesajı iletin.

execute yöntemi, uygulamanızın işleminde bir arka plan iş parçacığında çalışır. Bu nedenle, okuma ve yazma işlemleri tamamlanana kadar engellenebilir. Test çalıştırıcının UiAutomation gibi araçlarına erişmek için InstrumentationRegistry.getInstrumentation yöntemini çağırın.

Aşağıdaki örnekte, Proto DataStore'da bir tema ayarı oluşturulup doğrulanmaktadır. Burada settingsDataStore, uygulamanızda tanımlanan bir DataStore<Settings> özelliğidir:

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

import android.content.Context
import androidx.test.backup.BackupDeviceAction
import androidx.test.backup.BackupDeviceActionArgs
import androidx.test.backup.BackupDeviceActionResult
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking

class SeedThemeAction : BackupDeviceAction {
    override fun execute(
        context: Context,
        args: BackupDeviceActionArgs,
    ): BackupDeviceActionResult {
        val theme = args["theme"]
            ?: return BackupDeviceActionResult.failure("Missing theme argument")
        runBlocking {
            context.settingsDataStore.updateData {
                it.toBuilder().setTheme(theme).build()
            }
        }
        return BackupDeviceActionResult.success()
    }
}

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

runBackupRestoreFlow içinde doldurma ve doğrulama işlemlerini çalıştırmak için sınıf adlarını StorageDomain.CustomData'e iletin:

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

Başlangıç sırasında oluşturulan değerleri doğrulama işlemine iletmek için BackupDeviceActionResult.success öğesine bir eşleme iletin. CustomData, bu girişleri doğrulama işleminin bağımsız değişkenleriyle birleştirir.

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

Akışı özelleştirmek için (örneğin, bir yükseltmeyi test etmek için uninstallApp ve installApk komutlarını veya launchApp komutunu çağırmak) her adımı kendiniz çalıştırın:

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

Bir cihaz işlemi başarısız olduğunda runOnDevice, istisna oluşturmak yerine BackupActionResult.Failure döndürür. İşlem başarılı olduğunda döndürülen data eşlemesini almak için getOrThrow yöntemini çağırın veya işlem başarısız olduğunda IOException hatası verin.

Cihazlar arasında aktarım testi yapma

İki cihaz arasındaki aktarımı test etmek için her cihaz için bir BackupRestoreController parametresi tanımlayın ve hedef cihazı runBackupRestoreFlow içindeki restoreOn parametresine iletin:

package com.example.myapp

import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import androidx.test.backup.host.Device
import androidx.test.backup.host.StorageDomain
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir

@ExtendWith(BackupRestoreExtension::class)
class ThemeTransferTest {

    @Test
    fun themeTransfersToNewDevice(
        @Device(role = "source") source: BackupRestoreController,
        @Device(role = "target") target: BackupRestoreController,
        @TempDir outputDir: Path,
    ): Unit = runBlocking {
        source.runBackupRestoreFlow(
            storage = StorageDomain.DataStorePreference(
                dataStoreName = "settings",
                key = "theme",
                value = "dark",
            ),
            outputDir = outputDir,
            mode = BackupTransportMode.DEVICE_TO_DEVICE,
            restoreOn = target,
        )
    }
}

source cihazında akış, uygulamayı başlatır ve yedekler. target cihazında ise uygulama verilerini temizler, yedeği geri yükler ve verileri doğrular. LOCALYedekleme verileri kaynak cihazda kaldığı için başka bir cihazda geri yükleme yapmak için DEVICE_TO_DEVICE veya bulut aktarım modu gerekir.

@Device'nin role değerleri, testleri çalıştırırken her parametre için bir cihaz seçmenize olanak tanır. Yalnızca bir eşleşen cihaz bağlıysa her iki parametre de bu cihazı paylaşır ve aktarımı tek bir cihazda simüle eder.

Cihazları seçin

Varsayılan olarak test çerçevesi, online cihazları seri numarasına göre sıralar ve yeterli sayıda cihaz bağlı olduğunda her BackupRestoreController parametresine kendi eşleşen cihazını verir. Ayrı bir cihaz alamayan parametreler, eşleşen ilk cihazı paylaşır. ANDROID_SERIAL ortam değişkeni, cihaz atamasını etkilemez.

Testlerinize seri numarası eklemeden cihaz seçmek için her cihaz parametresine @Device(role = "...") ile bir rol verin. Ardından, testleri çalıştırırken her rolü androidx.test.backup.device.serial.{role} sistem özelliğiyle bir cihaza bağlayın. Örneğin, aşağıdaki komut, paketi kaynak cihaz olarak emulator-5554 ve hedef cihaz olarak emulator-5556 ile çalıştırır:

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

Bağlama yalnızca bu role sahip parametreler için geçerlidir. Diğer parametreler, eşleşen diğer cihazları alır.

Cihazları @Device üzerinde serial, api, minApi ve maxApi ile de filtreleyebilirsiniz (ör. @Device(role = "target", minApi = 35)). Seri numaralarını veya tam API düzeylerini komut satırından ayarlamak için aşağıdaki sistem özelliklerini -D ile iletin:

Sistem özelliği Açıklama
androidx.test.backup.device.serial.{role} @Device(role = "{role}") ile ek açıklama eklenmiş parametreler için cihazın seri numarası. serials, serial ve @Device'nin serial değerine göre önceliklidir.
androidx.test.backup.device.serials Cihaz parametrelerine sırayla atanan, virgülle ayrılmış seri numarası listesi. serial ve @Device politikasının serial değerine göre önceliklidir.
androidx.test.backup.device.serial serials tarafından kapsanmayan cihaz parametrelerinin seri numarası. @Device politikasının serial değerine göre önceliklidir.
androidx.test.backup.device.apis Cihaz parametrelerine sırayla atanan API düzeylerinin virgülle ayrılmış listesi. Herhangi bir API düzeyiyle eşleştirmek için 0 kullanın. @Device tarihinde, minApi ve maxApi içinde api ile değiştirilir.
androidx.test.backup.device.api apis tarafından kapsanmayan cihaz parametreleri için API düzeyi. Herhangi bir API düzeyiyle eşleştirmek için 0 kullanın. @Device tarihinde, minApi ve maxApi içinde api yerine geçer.

serial, serials, api ve apis özellikleri, ç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.

Test Amaçlı Kimlik Bilgilerini Geri Yükleme

Uygulamanız Kimlik Bilgilerini Geri Yükleme özelliğini kullanıyorsa geri yükleme anahtarının yedekleme ve geri yükleme işleminden sonra da kullanılabildiğini test edin. Bulut ve cihazlar arası modlar, anahtarı uygulamanızın verileriyle birlikte yedekler ancak LOCAL yedeklemez.

Anahtarı oluşturan ve Kimlik Bilgisi Yöneticisi ile alan cihaz işlemlerini yazın. Oluşturma işlemi, anahtarın kimlik bilgisi kimliğini döndürür. Böylece doğrulama işlemi, geri yükleme işleminin aynı anahtarı geri getirdiğini kontrol edebilir. Bu örnekte, createRequestJson ve getRequestJson, uygulamanızın Kimlik Bilgisi Yöneticisi'ne ilettiği istek JSON dizeleridir:

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

Aşağıdaki ana makine testi, kaynak cihazda anahtarı oluşturur, hedef cihazda geri yükler ve kimlik bilgisi kimliğinin eşleştiğini doğrular:

package com.example.myapp

import androidx.test.backup.host.BackupRestoreController
import androidx.test.backup.host.BackupRestoreExtension
import androidx.test.backup.host.BackupTransportMode
import androidx.test.backup.host.Device
import androidx.test.backup.host.StorageDomain
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir

const val CREATE_RESTORE_KEY = "com.example.myapp.CreateRestoreKeyAction"
const val VERIFY_RESTORE_KEY = "com.example.myapp.VerifyRestoreKeyAction"

@ExtendWith(BackupRestoreExtension::class)
class RestoreKeyTest {

    @Test
    fun restoreKeyTransfersToNewDevice(
        @Device(role = "source") source: BackupRestoreController,
        @Device(role = "target") target: BackupRestoreController,
        @TempDir outputDir: Path,
    ): Unit = runBlocking {
        // Clears target (including any existing restore key) before restoring.
        source.runBackupRestoreFlow(
            storage = StorageDomain.CustomData(
                populateAction = CREATE_RESTORE_KEY,
                verifyAction = VERIFY_RESTORE_KEY,
            ),
            outputDir = outputDir,
            mode = BackupTransportMode.DEVICE_TO_DEVICE,
            restoreOn = target,
        )
    }
}

isCloudBackupEnabled değeri varsayılan olarak true şeklinde ayarlanmış bir anahtar oluşturmak için kaynak cihazda yedeklemenin etkin olduğu bir Google Hesabı ve ekran kilidi olması gerekir. Aksi takdirde, Kimlik Bilgisi Yöneticisi E2eeUnavailableException hatası verir.

Yeni bir cihazda buluttan geri yükleme işleminden farklı olarak bulut modları, aynı Google Hesabı'nı veya kaynak cihazın ekran kilidini gerektirmez ve isCloudBackupEnabled, false olarak ayarlanmış şekilde oluşturulan anahtarları geri yükler.

Testler arasında uygulama verilerini saklama

Test çerçevesi, her testten önce testin kullandığı her cihazdaki uygulama verilerinizi temizler. Bir test sınıfı veya yöntemi için bu özelliği devre dışı bırakmak istiyorsanız @Isolation(IsolationPolicy.MANUAL) ile ek 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 (ör. app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html).

Bir test başarısız olduğunda test çerçevesi, testin kullandığı her cihazdan logcat'i kaydeder ve kaydedilen dosya yollarını hata çıkışında listeler. Günlük kaydetmeyi devre dışı bırakmak için -Dandroidx.test.backup.device.logs.enabled=false değerini iletin.

Başarısız geri yükleme işlemlerini giderme

Buluttan veya cihazdan cihaza geri yükleme işlemi, aşağıdaki durumlarda hata mesajı vererek başarısız olur:

  • Google Play Hizmetleri uygulamanızı atlar. Örneğin, uygulama kimliği com.google. veya com.android. ile başladığı, yedeği boş olduğu ya da android:allowBackup="false" ayarını yaptığı için. Bu tür bir uygulama kimliğine sahip bir uygulamayı test etmek için LOCAL modunu kullanın.
  • API düzeyi 36.1 veya daha yüksek olan bir cihazda ya da emülatörde Google Play Hizmetleri sürümü 26.33.30'dan düşükse Cihazdaki Play Store üzerinden Google Play Hizmetleri'ni güncelleyin veya 26.33.30 ya da daha yeni bir sürümle birlikte gelen bir sistem görüntüsü kullanın.

Bilinen sorunlar ve sınırlamalar

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

  • Android Studio'da test paketleri henüz desteklenmemektedir. Projeniz doğru şekilde yapılandırılmış olsa bile kod düzenleyici, 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.