자동 백업 및 복원 테스트

사용자가 새 기기를 설정하거나 앱을 다시 설치하면 Android에서 클라우드 백업 또는 기기 간 전송을 통해 앱의 데이터를 복원할 수 있습니다. 백업 규칙에서 중요한 데이터를 제외하거나 앱에서 복원하는 데이터를 사용할 수 없는 경우 사용자에게 오류가 표시되지 않고 설정과 진행 상황이 손실됩니다.

자동 백업 및 복원 테스트를 사용하면 앱을 출시하기 전에 이러한 문제를 포착할 수 있습니다. 이러한 테스트는 개발 머신에서 실행되고 Android 디버그 브리지 (adb)를 통해 연결된 기기를 제어하는 JUnit 5 테스트입니다. 각 테스트는 기기에 앱 데이터를 시드하고 Android 백업 프레임워크를 통해 앱을 백업 및 복원한 다음 데이터가 손상되지 않았는지 확인합니다. 모듈의 빌드 파일에서 Android Gradle 플러그인 (AGP) 테스트 모음으로 테스트를 구성합니다.

작동 방식

백업 및 복원 테스트 모음은 함께 작동하는 두 부분으로 구성됩니다.

  • 호스트 테스트 는 개발 머신에서 실행되는 JUnit 5 테스트입니다. 각 기기에 BackupRestoreController를 사용하여 데이터를 시드하고 앱을 백업 및 복원하며 결과를 확인합니다.
  • 기기 작업 은 BackupDeviceAction을 구현하는 클래스입니다. 기기의 앱 프로세스에서 실행되므로 앱 자체 코드를 통해 데이터를 읽고 쓸 수 있습니다. 호스트 테스트는 클래스 이름으로 기기 작업을 호출합니다.

모음을 실행하면 AGP가 앱과 기기 작업이 포함된 테스트 APK를 빌드합니다. 각 테스트 전에 테스트 프레임워크는 테스트에서 사용하는 각 기기에 두 APK를 모두 설치한 다음 앱의 데이터를 삭제합니다.

기본 제공 스토리지 유형

다음 표에는 기기 작업을 작성하지 않고 데이터를 시드하고 확인할 수 있는 StorageDomain 하위 클래스가 나와 있습니다.

스토리지 도메인 데이터
StorageDomain.Preference SharedPreferences 파일의 키입니다.
StorageDomain.Database SQLite 데이터베이스 테이블의 행입니다.
StorageDomain.TextFile 앱의 내부 저장소에 있는 텍스트 파일입니다.
StorageDomain.BinaryFile 앱의 내부 저장소에 있는 바이너리 파일입니다.

DataStore 파일과 같은 다른 데이터를 테스트하려면 기기 작업 을 작성하세요.

전송 모드

각 백업은 BackupTransportMode 값을 사용합니다. 다음 표에는 지원되는 전송 모드가 나와 있습니다.

모드 시나리오
LOCAL 기기의 로컬 백업 전송에 대한 백업입니다. 백업 데이터는 기기에 유지됩니다.
CLOUD_ENCRYPTED 클라이언트 측 암호화가 사용 설정된 기기에서 사용자의 Google 계정에 저장된 것과 같은 클라우드 백업입니다.
CLOUD_UNENCRYPTED 클라이언트 측 암호화가 사용 설정되지 않은 기기의 클라우드 백업입니다.
DEVICE_TO_DEVICE 기기 간 전송입니다(예: 케이블 또는 Wi-Fi를 통한 전송).

LOCAL은 지원되는 모든 기기에서 작동합니다. 클라우드 및 기기 간 모드는 Android 스튜디오의 앱 데이터 백업 작업과 동일한 백업을 만듭니다. 백업은 다른 기기에 업로드되거나 전송되지 않고 개발 머신에 저장됩니다. 클라우드 및 기기 간 모드에는 기기의 Google Play 서비스와 디버깅 가능한 앱 빌드가 필요합니다. 백업 규칙이 클라이언트 측 암호화에 종속된 경우 두 클라우드 모드를 모두 테스트하세요.

요구사항

자동 백업 및 복원 테스트를 사용하려면 환경이 다음 요구사항을 충족해야 합니다.

  • Gradle 버전 9.6.0 이상
  • JDK 버전 17 이상
  • adb를 통해 연결된 Android 12 (API 수준 31) 이상을 실행하는 기기 또는 에뮬레이터 두 기기 간의 전송을 테스트하려면 두 기기를 연결하세요.

호환성

다음 표에는 자동 백업 및 복원 테스트의 최소 버전과 권장 버전이 나와 있습니다.

구성요소 최소 버전 권장 버전
Android 스튜디오 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-alpha02 이상

각 AGP 미리보기 버전에는 일치하는 Android 스튜디오 미리보기 버전이 필요합니다. 자세한 내용은 AGP 미리보기를 참고하세요.

프로젝트 설정

앱 모듈에 백업 및 복원 테스트 모음을 추가하려면 다음 단계를 완료하세요.

실험용 플래그 사용 설정

프로젝트의 gradle.properties 파일에서 테스트 모음과 백업 테스트를 사용 설정합니다.

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

테스트 모음 선언

앱 모듈의 build.gradle.kts 파일에서 testOptions 블록에 백업 테스트 모음을 선언합니다.

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

이 예의 모음 이름인 backupTest는 소스 세트 디렉터리와 Gradle 작업 이름을 결정합니다. 다음 선택적 속성으로 모음을 맞춤설정할 수 있습니다.

  • backupTestLibraryVersion을 사용하여 AGP가 모음에 추가하는 androidx.test.backup 라이브러리의 버전(예: "1.0.0-alpha02")을 설정합니다. 이 속성은 선택사항입니다. 이 속성을 생략하면 AGP는 AGP 버전을 기반으로 기본 라이브러리 버전으로 대체됩니다.
  • targetVariants를 사용하여 모음에서 테스트하는 빌드 변형(예: targetVariants.add("demoDebug"))을 지정합니다. 이 속성은 선택사항입니다. 이 속성을 생략하면 모음은 모듈의 testBuildType 변형을 테스트합니다. 기본적으로 debug입니다. 클라우드 및 기기 간 전송 모드에는 디버깅 가능한 앱이 필요하므로 디버깅 가능한 변형을 테스트하는 것이 좋습니다.

AGP는 호스트 라이브러리 androidx.test.backup:backup-host와 JUnit 5를 호스트 테스트에 추가합니다. 테스트 APK에 기기 라이브러리 androidx.test.backup:backup을 추가하고 테스트 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) 는 연결된 기기를 테스트에 제공합니다.
  • @BackupRestoreConfig(applicationId = "...")는 테스트 중인 앱의 애플리케이션 ID를 설정합니다. applicationIdSuffix를 포함하여 테스트하는 변형의 애플리케이션 ID를 사용합니다.

각 테스트 메서드는 사용하는 각 기기에 BackupRestoreController 매개변수를 선언합니다.

기본 제공 스토리지 유형 테스트

기본 제공 스토리지 유형을 테스트하려면 시드할 데이터, 백업 파일의 디렉터리, 전송 모드를 사용하여 runBackupRestoreFlow를 호출합니다. 이 메서드는 기기에서 다음 단계를 실행합니다.

  1. 각 스토리지 도메인을 시드합니다.
  2. 앱을 중지합니다.
  3. 앱을 백업합니다.
  4. 앱의 데이터를 삭제합니다.
  5. 백업을 복원합니다.
  6. 각 스토리지 도메인에 시드된 값이 있는지 확인합니다.

단계가 실패하면 메서드는 예외를 발생시켜 테스트를 실패시킵니다. 다음 예에서는 로컬 백업 전송을 사용하여 SharedPreferences 키, SQLite 행, 내부 파일을 테스트합니다.

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

스토리지 도메인을 정의할 때는 다음 사항에 유의하세요.

  • Preference는 SharedPreferences 파일의 이름을 .xml 확장자 없이 가져옵니다.
  • TextFile 및 BinaryFile은 앱의 filesDir에 대해 상대 경로를 확인합니다.
  • Database 는 SQL로 행을 쓰고 테이블이 없는 경우 TEXT 열이 있는 테이블을 만듭니다. 앱이 Room을 사용하는 경우 데이터베이스에 Room이 예상하는 스키마가 있도록 기기 작업을 사용하는 데이터베이스 콘텐츠를 테스트하는 것이 좋습니다.

기기 작업 작성

기본 제공 스토리지 유형에서 다루지 않는 데이터를 시드하거나 확인하려면 모음의 androidTest 소스 세트에 기기 작업을 작성하세요. 기기 작업은 BackupDeviceAction을 구현하고 두 멤버를 재정의합니다.

  • 데이터를 시드하는 작업의 경우 phase를 BackupDeviceAction.PHASE_POPULATE로 설정하고 데이터를 확인하는 작업의 경우 BackupDeviceAction.PHASE_VERIFY로 설정합니다.
  • 앱의 Context 및 호스트 테스트의 인수로 작업을 실행하도록 execute를 구현합니다. BackupDeviceActionResult.success 또는 BackupDeviceActionResult.failure 팩토리 메서드로 만든 결과를 반환하고 오류 메시지를 failure에 전달합니다.

execute 메서드는 백그라운드 스레드의 앱 프로세스에서 실행되므로 읽기 및 쓰기가 완료될 때까지 차단할 수 있습니다.

다음 예에서는 앱이 Preferences DataStore에 저장하는 테마 설정을 시드하고 확인합니다. 앱은 기본 소스 세트에서 DataStore를 정의합니다.

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

기기 작업은 동일한 정의를 사용합니다.

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

호스트 테스트에 데이터를 반환하려면 맵을 success 메서드에 전달합니다. 호스트 테스트는 BackupActionResult.Success.data에서 맵을 읽습니다.

흐름의 각 단계 실행

기기 작업을 사용하거나 흐름을 맞춤설정하려면 각 단계를 직접 호출하세요. 다음 호스트 테스트는 클라우드 백업 및 복원과 관련된 이전 섹션의 기기 작업을 실행합니다.

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

기기 작업이 실패하면 runOnDevice는 BackupActionResult.Failure 예외를 발생시키는 대신 반환하므로 결과를 확인하세요. 다른 단계가 실패하면 예외가 발생합니다.

기기 간 전송 테스트

두 기기 간의 전송을 테스트하려면 각 기기에 BackupRestoreController 매개변수를 선언합니다. 소스 기기에서 앱을 백업한 다음 대상 기기에서 백업 파일을 복원합니다. 다음 예에서는 이전 예의 상수와 assertSuccess 도우미를 재사용합니다.

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

role의 @Device 값은 테스트를 더 쉽게 읽을 수 있도록 하는 라벨입니다. 매개변수를 가져오는 기기에는 영향을 미치지 않습니다. 기기가 하나만 연결된 경우 두 매개변수 모두 해당 기기를 사용하고 테스트는 단일 기기에서 전송을 시뮬레이션합니다. 대상의 데이터를 삭제하면 복원된 데이터가 백업에서 가져온 것인지 확인할 수 있습니다.

기기 선택

기본적으로 테스트 프레임워크는 온라인 기기를 일련번호로 정렬하고 위치별로 BackupRestoreController 매개변수에 할당합니다. 예를 들어 테스트 메서드의 첫 번째 매개변수는 첫 번째 기기를 가져오고 두 번째 매개변수는 두 번째 기기를 가져옵니다. 위치에 기기가 없으면 매개변수는 첫 번째 기기를 가져옵니다. 위치는 메서드의 모든 매개변수를 계산하므로 @TempDir과 같은 다른 매개변수 앞에 기기 매개변수를 선언합니다. ANDROID_SERIAL 환경 변수는 기기 할당에 영향을 미치지 않습니다.

기기를 선택하려면 @Device의 serial 또는 api 값을 설정하거나 JVM 시스템 속성을 설정합니다. 시스템 속성은 @Device 값보다 우선합니다. 다음 표에는 지원되는 시스템 속성이 나와 있습니다.

시스템 속성 설명
androidx.test.backup.device.serial 모든 기기 매개변수의 기기 일련번호입니다.
androidx.test.backup.device.serials 위치별로 매개변수에 할당된 일련번호의 쉼표로 구분된 목록입니다.
androidx.test.backup.device.serial.{name} @Device(serial = "{name}")로 주석 처리된 매개변수의 일련번호입니다.
androidx.test.backup.device.api 모든 기기 매개변수의 기기 API 수준입니다.
androidx.test.backup.device.apis 위치별로 매개변수에 할당된 API 수준의 쉼표로 구분된 목록입니다.

명령줄에서 테스트를 실행할 때 -D로 이러한 속성을 전달합니다. 속성은 실행되는 모든 테스트에 적용되므로 --tests 옵션을 사용하여 필요한 테스트만 실행하세요. 예를 들어 다음 명령어는 emulator-5554를 소스 기기로, emulator-5556을 대상 기기로 사용하여 ThemeTransferTest를 실행합니다.

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

테스트 간 앱 데이터 유지

각 테스트 전에 테스트 프레임워크는 테스트에서 사용하는 각 기기에서 앱의 데이터를 삭제합니다. 테스트 클래스 또는 메서드에서 이 기능을 사용 중지하려면 @Isolation(IsolationPolicy.MANUAL)로 주석 처리하세요.

Java로 테스트 작성

BackupRestoreController 메서드는 Kotlin 정지 함수입니다. Java로 호스트 테스트를 작성하는 경우 ListenableFuture를 반환하는 runBackupRestoreFlowAsync와 같은 각 메서드의 Async 변형을 사용하세요.

백업 및 복원 테스트 실행

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 테스트 보고서를 생성합니다.

알려진 문제 및 제한사항

자동 백업 및 복원 테스트에는 다음과 같은 알려진 문제와 제한사항이 있습니다.

  • 테스트 모음에 대한 Android 스튜디오 지원은 아직 제공되지 않습니다. 프로젝트가 올바르게 구성된 경우에도 코드 편집기에 테스트 모음 소스 파일에 해결되지 않은 참조 오류가 표시될 수 있습니다. 대신 Gradle을 사용하여 명령줄에서 백업 및 복원 테스트를 빌드하고 실행하세요.
  • 테스트는 adb를 통해 개발 머신에 연결된 기기 및 에뮬레이터에서만 실행됩니다. Firebase Test Lab은 지원되지 않습니다.
  • 테스트는 앱의 데이터를 삭제하고 사용하는 기기의 백업 설정을 변경합니다. 전용 테스트 기기를 사용하는 것이 좋습니다.

각 출시의 변경사항은 백업 및 복원 테스트 출시 노트를 참고하세요.