バックアップと復元の自動テスト

ユーザーが新しいデバイスをセットアップしたり、アプリを再インストールしたりすると、Android はクラウド バックアップまたはデバイス間の転送からアプリのデータを復元できます。バックアップ ルールで重要なデータが除外されている場合や、アプリが復元したデータを使用できない場合、ユーザーはエラーが表示されることなく設定や進行状況を失うことになります。

自動バックアップと復元のテストは、アプリをリリースする前にこれらの問題を検出するのに役立ちます。これらは、開発マシンで実行され、Android Debug Bridge(adb)を介して接続されたデバイスを制御する JUnit 5 テストです。各テストでは、デバイスにアプリデータをシードし、Android バックアップ フレームワークを介してアプリのバックアップと復元を行い、データがそのまま残っていることを確認します。テストは、モジュールのビルドファイルで Android Gradle プラグイン(AGP)のテストスイートとして構成します。

仕組み

バックアップと復元のテストスイートは、連携して動作する次の 2 つの部分で構成されています。

  • ホストテストは、開発マシンで実行される JUnit 5 テストです。各デバイスで BackupRestoreController を使用して、データのシード、アプリのバックアップと復元、結果の確認を行います。
  • デバイス アクションは、BackupDeviceAction を実装するクラスです。デバイス上のアプリのプロセスで実行されるため、アプリ独自のコードを介してデータの読み取りと書き込みを行うことができます。ホストテストは、クラス名でデバイス アクションを呼び出します。

スイートを実行すると、AGP はアプリと、デバイス アクションを含むテスト APK をビルドします。テスト フレームワークは、テストで使用する各デバイスに両方の APK をインストールし、各テストの前にアプリのデータを消去します。

組み込みストレージ タイプ

次の表に、デバイス アクションを記述せずにデータのシードと検証を行うことができる StorageDomain サブクラスを示します。

ストレージ ドメイン データ
StorageDomain.Preference SharedPreferences ファイル内の鍵。
StorageDomain.DataStorePreference Preferences DataStore ファイル内のキー。
StorageDomain.Database SQLite データベース テーブルの行。
StorageDomain.TextFile アプリの内部ストレージにあるテキスト ファイル。
StorageDomain.BinaryFile アプリの内部ストレージにあるバイナリ ファイル。
StorageDomain.ExcludedData データを復元すべきでない、別の組み込みストレージ ドメイン(バックアップ ルールによるデータの除外など)。

Proto DataStore ファイルや Room データベースなどの他のデータをテストするには、デバイス アクションを記述します。

移動手段

各バックアップは BackupTransportMode 値を使用します。次の表に、サポートされている移動手段を示します。

モード シナリオ
LOCAL デバイスのローカル バックアップ トランスポートへのバックアップ。バックアップ データはデバイスに保存されます。
CLOUD_ENCRYPTED クラウド バックアップ(クライアントサイド暗号化が有効なデバイスからユーザーの Google アカウントに保存されたバックアップなど)。
CLOUD_UNENCRYPTED クライアントサイド暗号化が設定されていないデバイスからのクラウド バックアップ。
DEVICE_TO_DEVICE デバイス間の転送(ケーブルや Wi-Fi 経由など)。

LOCAL は、サポートされているすべてのデバイスで動作します。クラウドモードとデバイス間モードでは、Android Studio の [Backup App Data] アクションと同じアプリデータのバックアップが作成されます。バックアップは、アップロードまたは別のデバイスに送信されるのではなく、開発マシンに保存されます。クラウドモードとデバイス間モードでは、デバイスに Google Play 開発者サービスがインストールされていることと、アプリのデバッグ可能なビルドが必要です。バックアップ ルールがクライアントサイドの暗号化に依存している場合は、両方のクラウドモードをテストします。

要件

自動バックアップと復元のテストを使用するには、環境が次の要件を満たしていることを確認してください。

  • Gradle バージョン 9.6.0 以降。
  • JDK バージョン 17 以降。
  • Android 12(API レベル 31)以降を搭載し、adb 経由で接続されているデバイスまたはエミュレータ。
    • API レベル 36.1 以降のデバイスまたはエミュレータには、Google Play 開発者サービス バージョン 26.33.30 以降が必要です。
    • 2 台のデバイス間の転送をテストするには、2 台のデバイスを接続します。

互換性

次の表に、自動バックアップと復元のテストの最小バージョンと推奨バージョンを示します。

コンポーネント 最小バージョン 推奨バージョン
Android Studio Rabbit 2 Canary 1 Rabbit 2 Canary 4 以降
Android Gradle プラグイン(AGP) 9.5.0-alpha06 9.5.0-alpha09 以降
androidx.test.backup 個のライブラリ 1.0.0-alpha01 1.0.0-alpha04 以降

AGP の各プレビュー版には、対応するプレビュー版の Android Studio が必要です。詳しくは、AGP プレビューをご覧ください。

プロジェクトを設定する

バックアップと復元のテストスイートをアプリ モジュールに追加する手順は次のとおりです。

試験運用版フラグを有効にする

プロジェクトの gradle.properties ファイルで、テストスイートとバックアップ テストを有効にします。

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

テストスイートを宣言する

アプリ モジュールの build.gradle.kts ファイルで、testOptions ブロックにバックアップ テストスイートを宣言します。

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

この例のスイート名 backupTest によって、ソースセット ディレクトリと Gradle タスク名が決まります。次のオプション プロパティを使用して、スイートをカスタマイズできます。

  • backupTestLibraryVersion を使用して、AGP がスイートに追加する androidx.test.backup ライブラリ("1.0.0-alpha04" など)のバージョンを設定します。このプロパティは省略可能です。省略すると、AGP は AGP バージョンに基づいてデフォルトのライブラリ バージョンにフォールバックします。このページの例では、バージョン 1.0.0-alpha04 以降が必要です。
  • targetVariants を使用して、スイートがテストするビルド バリアント(targetVariants.add("demoDebug") など)を指定します。このプロパティは省略可能です。省略すると、スイートはモジュールの testBuildType のバリアントをテストします。デフォルトは debug です。クラウドとデバイス間の転送モードではデバッグ可能なアプリが必要になるため、デバッグ可能なバリアントをテストすることをおすすめします。

AGP は、ホスト ライブラリ、androidx.test.backup:backup-host、JUnit 5 をホストテストに追加します。デバイス ライブラリ androidx.test.backup:backup をテスト APK に追加し、テスト APK のインストルメンテーション ランナーを設定します。

他の依存関係を追加するには、スイートの hostJar ブロックと testApk ブロックを使用します。たとえば、ホストテストで Truth を使用するには、hostJar ブロックに追加します。

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

ソースセットを作成する

スイート名と一致するソースセット ディレクトリを作成します。

  • ホストテストを src/{suiteName}/test/kotlin/ に配置します。
  • デバイス アクションを src/{suiteName}/androidTest/kotlin/ に配置します。

たとえば、backupTest という名前のスイートは、次のディレクトリ構造を使用します。

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

バックアップ構成を確認する

テストでは、アプリのバックアップ構成に含まれるデータのバックアップと復元を行います。アプリでバックアップが有効になっていること、およびバックアップ ルールにテストするデータが含まれていることを確認します。

バックアップと復元のテストを作成する

スイートの test ソースセットにホストテストを作成します。各テストクラスに @ExtendWith(BackupRestoreExtension::class) アノテーションを付けます。これにより、接続されたデバイスがテストに提供されます。テストは、スイートがターゲットとするアプリ バリアントに対して実行されるため、アプリケーション ID を設定する必要はありません。

各テストメソッドは、使用するデバイスごとに BackupRestoreController パラメータを宣言します。

組み込みストレージ タイプをテストする

組み込みのストレージ タイプをテストするには、シードするデータ、バックアップ ファイルのディレクトリ、トランスポート モードを指定して runBackupRestoreFlow を呼び出します。このメソッドは、デバイスで次の手順を実行します。

  1. 各ストレージ ドメインをシードします。
  2. アプリを停止します。
  3. アプリをバックアップします。
  4. アプリのデータを消去します。
  5. バックアップを復元します。
  6. 各ストレージ ドメインにシード値があること、または StorageDomain.ExcludedData でラップされている場合は存在しないことを確認します。

ステップが失敗すると、メソッドは例外をスローし、テストは失敗します。次の例では、ローカル バックアップ トランスポートを使用して、SharedPreferences、Preferences DataStore、SQLite、内部ファイル、除外ファイルをテストします。

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

ストレージ ドメインを定義する際は、次の点に注意してください。

  • Preference は、.xml 拡張子なしの SharedPreferences ファイルの名前を受け取ります。
  • DataStorePreference は preferencesDataStore に渡された名前を受け取ります。value には、アプリの設定キーと同じ Kotlin 型(floatPreferencesKey の場合は 1.25f など)を使用します。これにより、アプリは復元されたエントリを読み取ることができます。
  • TextFile と BinaryFile は、アプリの filesDir に対する相対パスを解決します。
  • Database は SQL で行を書き込み、テーブルが存在しない場合は TEXT 列を含むテーブルを作成します。アプリで Room を使用している場合は、DAO を使用するデバイス アクションでデータベースの内容をテストし、データベースが Room のスキーマと一致するようにします。
  • ExcludedData には、同じ runBackupRestoreFlow 呼び出しに、除外されていないストレージ ドメインが少なくとも 1 つ必要です。
  • Preference、DataStorePreference、Database、TextFile、BinaryFile は、isDeviceProtectedStorage = true を設定すると、アプリのデフォルト ストレージではなく、デバイスで保護されたストレージをターゲットにします。

デバイス アクションを書き込む

組み込みのストレージ タイプでカバーされていないデータをシードまたは検証するには、スイートの androidTest ソースセットにデバイス アクションを記述します。BackupDeviceAction を実装し、execute をオーバーライドして、アプリの Context とホストテストの引数を使用してアクションを実行します。BackupDeviceActionResult.success または BackupDeviceActionResult.failure で作成された BackupDeviceActionResult を返し、エラー メッセージを failure に渡します。

execute メソッドは、バックグラウンド スレッドのアプリのプロセスで実行されるため、読み取りと書き込みが完了するまでブロックできます。UiAutomation などのテストランナーのインストルメンテーションにアクセスするには、InstrumentationRegistry.getInstrumentation を呼び出します。

次の例では、Proto DataStore でテーマ設定をシードして検証します。ここで、settingsDataStore はアプリで定義されている DataStore<Settings> プロパティです。

// 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 で入力アクションと検証アクションを実行するには、クラス名を 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,
        )
    }
}

シーディング中に生成された値を検証アクションに渡すには、BackupDeviceActionResult.success にマップを渡します。CustomData は、これらのエントリを検証アクションの引数にマージします。

フローの各ステップを実行する

フローをカスタマイズするには(たとえば、uninstallApp と installApk を呼び出してアップグレードをテストする場合や、launchApp を呼び出す場合)、各ステップを自分で実行します。

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

デバイス アクションが失敗すると、runOnDevice は例外をスローする代わりに BackupActionResult.Failure を返します。getOrThrow を呼び出して、成功した場合は返された data マップを取得し、失敗した場合は IOException をスローします。

デバイス間の転送をテストする

2 つのデバイス間の転送をテストするには、デバイスごとに BackupRestoreController パラメータを宣言し、runBackupRestoreFlow の restoreOn に対象デバイスを渡します。

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 では、フローがアプリをシードしてバックアップします。target では、アプリデータを消去し、バックアップを復元して、データを検証します。LOCAL のバックアップ データはソースデバイスに残るため、別のデバイスに復元するには DEVICE_TO_DEVICE またはクラウド転送モードが必要です。

@Device の role 値を使用すると、テストの実行時にパラメータごとにデバイスを選択できます。一致するデバイスが 1 つしか接続されていない場合、両方のパラメータがそのデバイスを共有し、1 つのデバイスで転送をシミュレートします。

デバイスの選択

デフォルトでは、テスト フレームワークはオンライン デバイスをシリアル番号で並べ替え、十分な数のデバイスが接続されている場合は、各 BackupRestoreController パラメータに独自の一致するデバイスを割り当てます。個別のデバイスを取得できないパラメータは、最初に一致したデバイスを共有します。ANDROID_SERIAL 環境変数はデバイスの割り当てに影響しません。

テストにシリアル番号を入れずにデバイスを選択するには、@Device(role = "...") を使用して各デバイス パラメータにロールを割り当てます。次に、テストを実行するときに、androidx.test.backup.device.serial.{role} システム プロパティを使用して各ロールをデバイスにバインドします。たとえば、次のコマンドは、emulator-5554 をソースデバイス、emulator-5556 を対象デバイスとしてスイートを実行します。

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

バインディングは、そのロールを持つパラメータにのみ適用されます。その他のパラメータは、一致する残りのデバイスを取得します。

@Device で serial、api、minApi、maxApi を使用してデバイスをフィルタすることもできます(例: @Device(role = "target", minApi = 35))。コマンドラインからシリアル番号または正確な API レベルを設定するには、-D を使用して次のシステム プロパティを渡します。

システム プロパティ 説明
androidx.test.backup.device.serial.{role} @Device(role = "{role}") でアノテーションが付けられたパラメータのデバイスのシリアル番号。serials、serial、@Device の serial 値よりも優先されます。
androidx.test.backup.device.serials シリアル番号のカンマ区切りのリスト。デバイス パラメータに順番に割り当てられます。serial と @Device の serial 値よりも優先されます。
androidx.test.backup.device.serial serials でカバーされていないデバイス パラメータのシリアル番号。@Device の serial 値よりも優先されます。
androidx.test.backup.device.apis API レベルのカンマ区切りのリスト。デバイス パラメータに順番に割り当てられます。0 を使用して、任意の API レベルに一致させます。@Device の api を minApi と maxApi に置き換えます。
androidx.test.backup.device.api apis でカバーされていないデバイス パラメータの API レベル。0 を使用して、任意の API レベルに一致させます。@Device の api を minApi と maxApi に置き換えます。

serial、serials、api、apis プロパティは、実行されるすべてのテストに適用されるため、--tests オプションを使用して、それらを必要とするテストのみを実行します。

テスト用認証情報の復元

アプリで Restore Credentials を使用している場合は、バックアップと復元を行っても復元キーが保持されることをテストします。クラウド モードとデバイス間モードでは、アプリのデータとともに鍵がバックアップされますが、LOCAL ではバックアップされません。

キーを作成し、認証情報マネージャーで取得するデバイス アクションを作成します。作成アクションは鍵の認証情報 ID を返します。これにより、検証アクションは復元によって同じ鍵が戻されることを確認できます。この例では、createRequestJson と getRequestJson は、アプリが認証情報マネージャーに渡すリクエスト JSON 文字列です。

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

次のホストテストでは、ソースデバイスで鍵を作成し、対象デバイスで鍵を復元して、認証情報 ID が一致することを確認します。

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 をデフォルトの true に設定してキーを作成するには、ソースデバイスにバックアップが有効になっている Google アカウントと画面ロックが必要です。それ以外の場合、認証情報マネージャーは E2eeUnavailableException をスローします。

新しいデバイスでのクラウド復元とは異なり、クラウドモードでは、同じ Google アカウントやソースデバイスの画面ロックは必要ありません。また、isCloudBackupEnabled を false に設定して作成された鍵が復元されます。

テスト間でアプリデータを保持する

各テストの前に、テスト フレームワークは、テストで使用する各デバイス上のアプリのデータを消去します。テストクラスまたはメソッドでこの機能をオフにするには、@Isolation(IsolationPolicy.MANUAL) アノテーションを付けます。

Java でテストを作成する

BackupRestoreController メソッドは Kotlin の suspend 関数です。Java でホストテストを作成する場合は、各メソッドの Async バリアント(ListenableFuture を返す runBackupRestoreFlowAsync など)を使用します。

バックアップと復元のテストを実行する

AGP は、スイートがテストするバリアントごとに test{Variant}{SuiteName}DefaultTestSuite という名前の Gradle タスクを作成します。debug バリアントに対して backupTest スイートを実行するには、デバイスを接続して次のタスクを実行します。

./gradlew :app:testDebugBackupTestDefaultTestSuite

check、test、connectedCheck タスクはバックアップと復元のテストスイートを実行しないため、スイートのタスクを明示的に実行します。

単一のテストクラスまたはメソッドを実行するには、--tests オプションを使用します。

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

テストレポートを調べる

テスト実行が完了すると、AGP は {module}/build/reports/tests/{taskName}/index.html(app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html など)に HTML テストレポートを生成します。

テストが失敗すると、テスト フレームワークはテストで使用された各デバイスから logcat を保存し、保存されたファイルパスを失敗出力に一覧表示します。ログの保存をオフにするには、-Dandroidx.test.backup.device.logs.enabled=false を渡します。

復元の失敗に関するトラブルシューティング

次の場合、クラウドまたはデバイス間の復元が失敗し、エラー メッセージが表示されます。

  • Google Play 開発者サービスがアプリをスキップする。たとえば、アプリケーション ID が com.google. または com.android. で始まる、バックアップが空である、android:allowBackup="false" が設定されているなどの理由が考えられます。このようなアプリケーション ID を持つアプリをテストするには、LOCAL モードを使用します。
  • API レベル 36.1 以降を搭載したデバイスまたはエミュレータで、Google Play 開発者サービスのバージョンが 26.33.30 未満である。デバイスの Google Play ストアから Google Play 開発者サービスを更新するか、バージョン 26.33.30 以降に付属のシステム イメージを使用してください。

既知の問題と制限事項

自動バックアップと復元のテストには、以下の既知の問題と制限事項があります。

  • Android Studio では、テストスイートはまだサポートされていません。プロジェクトが正しく構成されている場合でも、コードエディタでテストスイートのソースファイルに未解決の参照エラーが表示されることがあります。代わりに、Gradle を使用してコマンドラインからバックアップと復元のテストをビルドして実行します。
  • テストは、adb を介して開発マシンに接続されているデバイスとエミュレータでのみ実行されます。Firebase Test Lab はサポートされていません。
  • テストでは、アプリのデータが消去され、使用するデバイスのバックアップ設定が変更されます。専用のテストデバイスを使用することをおすすめします。

各リリースの変更点については、バックアップと復元のテストのリリースノートをご覧ください。