وقتی کاربران دستگاه جدیدی را راهاندازی میکنند یا برنامهتان را بازنصب میکنند، Android میتواند دادههای برنامهتان را از نسخه پشتیبان ابری یا انتقال دستگاهبهدستگاه بازیابی کند. اگر قوانین پشتیبانگیری شما دادههای مهم را حذف کند، یا برنامهتان نتواند از دادههایی که بازیابی میکند استفاده کند، کاربران تنظیمات و پیشرفت خود را بدون دیدن خطا ازدست میدهند.
آزمایشهای خودکارسازیشده پشتیبانگیری و بازیابی به شما کمک میکنند این مشکلات را قبلاز انتشار برنامهتان شناسایی کنید. این آزمایشها JUnit 5 هستند که در ماشین توسعه نرمافزار شما اجرا میشوند و دستگاههای متصلشده را ازطریق «پل اشکالزدایی Android» (adb) کنترل میکنند. هر آزمایش دادههای برنامه را در دستگاهی قرار میدهد، از برنامه ازطریق چارچوب پشتیبانگیری Android پشتیبانگیری و بازیابی میکند، و سپس بررسی میکند که دادهها دستنخورده باشند. آزمایشها را بهعنوان مجموعه آزمون افزایه Android Gradle (AGP) در فایل ساخت واحدتان پیکربندی میکنید.
روش کار
مجموعه آزمون پشتیبانگیری و بازیابی دو بخش دارد که باهم کار میکنند:
- آزمایشهای میزبان آزمایشهای JUnit 5 هستند که در ماشین توسعه نرمافزار شما اجرا میشوند. آنها
از
BackupRestoreControllerبرای هر دستگاه برای کاشت داده، پشتیبانگیری و بازیابی برنامه شما، و بررسی نتایج استفاده میکنند. - کنشهای دستگاه کلاسهایی هستند که
BackupDeviceActionرا پیادهسازی میکنند. این کیتهای توسعه نرمافزار در فرایند برنامهتان در دستگاه اجرا میشوند، بنابراین میتوانند دادهها را ازطریق کد خود برنامهتان بخوانند و بنویسند. Host tests call device actions by class name.
وقتی مجموعه را اجرا میکنید، 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 ایجاد میکند:
نسخه پشتیبان بهجای بارگذاری یا ارسال به دستگاه دیگر، در ماشین توسعه نرمافزار شما ذخیره میشود. حالتهای ابری و دستگاهبهدستگاه به خدمات Google Play در دستگاه و نسخه اشکالزدایی برنامه شما نیاز دارند. اگر قوانین پشتیبانگیری شما به رمزگذاری سمت کارخواه بستگی دارد، هر دو حالت ابری را آزمایش کنید.
الزامات
برای استفاده از آزمایش پشتیبانگیری و بازیابی خودکار، مطمئن شوید محیط شما الزامات زیر را برآورده میکند:
- Gradle نسخه ۹.۶.۰ یا بالاتر.
- نسخه ۱۷ یا بالاتر JDK.
- دستگاه یا شبیهسازی که Android نسخه ۱۲ (سطح میانای برنامهسازی کاربردی ۳۱) یا بالاتر را اجرا میکند و
ازطریق adb متصل شده است:
- دستگاهها یا شبیهسازهای دارای سطح میانای برنامهسازی کاربردی ۳۶.۱ یا بالاتر به خدمات Google Play نسخه ۲۶.۳۳.۳۰ یا بالاتر نیاز دارند.
- برای آزمایش انتقال بین دو دستگاه، دو دستگاه را متصل کنید.
سازگاری
جدول زیر حداقل نسخهها و نسخههای توصیهشده برای آزمایش خودکار پشتیبانگیری و بازیابی را فهرست میکند:
| مؤلفه | حداقل نسخه | نسخه توصیهشده |
|---|---|---|
| استودیو 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-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برای تنظیم نسخه کتابخانههایandroidx.test.backupکه AGP به مجموعه اضافه میکند، مانند"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) حاشیهنویسی کنید، که دستگاههای متصلشده را
به آزمایشها ارائه میدهد. آزمایشها درمقابل نوع برنامهای که مجموعه هدف قرار میدهد اجرا میشوند، بنابراین
نیازی نیست شناسه برنامه آن را تنظیم کنید.
هر روش آزمایش یک پارامتر BackupRestoreController برای هر دستگاهی که استفاده میکند اعلام میکند.
آزمایش انواع فضای ذخیرهسازی داخلی
برای آزمایش انواع فضای ذخیرهسازی داخلی، runBackupRestoreFlow را با
دادههای موردنظر برای کاشت، دایرکتوری فایل پشتیبان، و حالت انتقال فراخوانی کنید. روش
مراحل زیر را در دستگاه اجرا میکند:
- هر دامنه فضای ذخیرهسازی را بذرپاشی میکند.
- برنامه را متوقف میکند.
- از برنامه پشتیبانگیری میکند.
- دادههای برنامه را پاک میکند.
- نسخه پشتیبان را بازیابی میکند.
- تأیید میکند که هر دامنه فضای ذخیرهسازی دارای مقدار بنیادی است یا وقتی در
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نام فایلSharedPreferencesرا بدون پسوند.xmlمیگیرد.-
DataStorePreferenceنامی را که بهpreferencesDataStoreمنتقل شده است میگیرد. از همان نوع Kotlin برایvalueبهعنوان کلید اولویت برنامهتان استفاده کنید، مثلاً1.25fبرایfloatPreferencesKey، تا برنامهتان بتواند ورودی بازیابیشده را بخواند. TextFileوBinaryFileمسیرهای نسبی را درمقابلfilesDirبرنامهتان حل میکنند.Databaseردیف را با SQL مینویسد و اگر جدول وجود نداشته باشد، جدول را با ستونهایTEXTایجاد میکند. اگر برنامهتان از Room استفاده میکند، محتوای پایگاه داده را با کنشهای دستگاه که از DAO شما استفاده میکنند آزمایش کنید تا پایگاه داده با طرحواره Room مطابقت داشته باشد.ExcludedDataبه حداقل یک دامنه فضای ذخیرهسازی مستثنینشده در همان فراخوانیrunBackupRestoreFlowنیاز دارد.-
Preference،DataStorePreference،Database،TextFile، وBinaryFileوقتیisDeviceProtectedStorage = trueرا تنظیم میکنید، بهجای فضای ذخیرهسازی پیشفرض برنامهتان، فضای ذخیرهسازی محافظتشده دستگاه را هدف قرار میدهند.
نوشتن کنشهای دستگاه
برای کاشتن یا درستیسنجی دادههایی که انواع فضای ذخیرهسازی داخلی پوشش نمیدهند،
کنشهای دستگاه را در مجموعه منبع androidTest مجموعه بنویسید. BackupDeviceAction را پیادهسازی کنید و execute را لغو کنید تا کنش با Context برنامهتان و آرگومانهای آزمایش میزبان اجرا شود. یک
BackupDeviceActionResult ایجادشده با
BackupDeviceActionResult.success یا BackupDeviceActionResult.failure را برگردانید،
و پیام خطایی را به 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 را برمیگرداند.
برای دریافت نقشه data برگرداندهشده درصورت موفقیت،
getOrThrow را فراخوانی کنید یا درصورت ناموفق بودن، IOException را برگردانید.
آزمایش انتقال بین دستگاهها
برای آزمایش انتقال بین دو دستگاه، پارامتر BackupRestoreController را برای هر دستگاه اعلام کنید و دستگاه هدف را به restoreOn در runBackupRestoreFlow ارسال کنید:
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، دادههای برنامه را پاک میکند، نسخه پشتیبان را بازیابی میکند، و دادهها را تأیید میکند. بازیابی در دستگاه دیگر
به DEVICE_TO_DEVICE یا حالت انتقال ابری نیاز دارد، زیرا دادههای پشتیبانگیری LOCAL
در دستگاه مبدأ باقی میماند.
مقادیر role @Device به شما امکان میدهد
هنگام اجرای آزمایشها، برای هر پارامتر دستگاهی را انتخاب کنید. اگر
فقط یک دستگاه منطبق متصل باشد، هر دو پارامتر آن دستگاه را همرسانی میکنند و
انتقال را در یک دستگاه شبیهسازی میکنند.
انتخاب دستگاه
بهطور پیشفرض، چارچوب آزمایشی دستگاههای آنلاین را براساس شماره سریال مرتب میکند و
وقتی دستگاههای کافی متصل باشند، به هر پارامتر 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اتصال فقط برای پارامترهای دارای آن نقش اعمال میشود. پارامترهای دیگر دستگاههای منطبق باقیمانده را دریافت میکنند.
همچنین میتوانید دستگاهها را با serial، api، minApi، و maxApi در
@Device فیلتر کنید، مثلاً @Device(role = "target", minApi = 35). برای تنظیم شماره سریال یا سطح دقیق میانای برنامهسازی کاربردی از خط فرمان، خصوصیات سیستم زیر را با -D ارسال کنید:
| ویژگی سیستم | شرح |
|---|---|
androidx.test.backup.device.serial.{role} |
شماره سریال دستگاه برای پارامترهای حاشیهنویسیشده با @Device(role = "{role}"). بر serials، serial، و مقدار serial از @Device اولویت دارد. |
androidx.test.backup.device.serials |
فهرست جداشده با کاما از شمارههای سریال که بهترتیب به پارامترهای دستگاه اختصاص داده شدهاند. بر serial و مقدار serial @Device اولویت دارد. |
androidx.test.backup.device.serial |
شماره سریال برای پارامترهای دستگاه که serials پوشش نمیدهد. بر مقدار serial از @Device اولویت دارد. |
androidx.test.backup.device.apis |
فهرست جداشده با کاما از سطوح میانای برنامهسازی کاربردی، که بهترتیب به پارامترهای دستگاه اختصاص داده شده است. برای مطابقت با هر سطح API، از 0 استفاده کنید. api را در @Device، در minApi و maxApi جایگزین میکند. |
androidx.test.backup.device.api |
سطح میانای برنامهسازی کاربردی برای پارامترهای دستگاهی که apis پوشش نمیدهد. برای مطابقت با هر سطح API، از 0 استفاده کنید. api را در @Device، در minApi و maxApi جایگزین میکند. |
ویژگیهای serial، serials، api، و apis برای هر آزمایشی که اجرا میشود اعمال میشود، بنابراین از گزینه --tests برای اجرای آزمایشهایی که به آنها نیاز دارند استفاده کنید.
آزمایش بازگرداندن اعتبارنامهها
اگر برنامهتان از بازگرداندن اعتبارنامهها استفاده میکند، آزمایش کنید که کلید بازگرداندن آن پساز پشتیبانگیری و بازگرداندن باقی بماند. حالتهای ابری و دستگاهبهدستگاه
کلید را با دادههای برنامهتان پشتیبانگیری میکنند، اما LOCAL این کار را نمیکند.
کنشهای دستگاه را بنویسید که کلید را ایجاد میکند و آن را با Credential Manager دریافت میکند. کنش
ایجاد، شناسه اطلاعات کاربری کلید را برمیگرداند تا کنش درستیسنجی بتواند
بررسی کند که بازیابی همان کلید را برمیگرداند. در این مثال،
createRequestJson و getRequestJson رشتههای JSON درخواست هستند که
برنامه شما به Credential Manager ارسال میکند:
// 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")
}
}
}
آزمایش میزبان زیر کلید را در دستگاه مبدأ ایجاد میکند، آن را در دستگاه هدف بازیابی میکند، و تأیید میکند که شناسه اعتبارنامه مطابقت دارد:
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) حاشیهنویسی کنید.
نوشتن آزمایشها در جاوا
روشهای BackupRestoreController توابع تعلیق Kotlin هستند. اگر آزمایشهای میزبان را در جاوا مینویسید، از گونه Async هر روش استفاده کنید، مثلاً runBackupRestoreFlowAsync که ListenableFuture را برمیگرداند.
اجرای آزمایشهای پشتیبانگیری و بازیابی
AGP برای هر گونهای که مجموعه آزمایش میکند، یک تکلیف Gradle ایجاد میکند که نام آن
test{Variant}{SuiteName}DefaultTestSuite است. برای اجرای مجموعه backupTest
درمقابل گونه debug، دستگاههایتان را متصل کنید و تکلیف را اجرا کنید:
./gradlew :app:testDebugBackupTestDefaultTestSuiteتکالیف check، test، و connectedCheck مجموعه آزمونهای پشتیبانگیری و بازیابی را اجرا نمیکنند، بنابراین تکلیف مجموعه را بهطور صریح اجرا کنید.
برای اجرای یک کلاس یا روش آزمایشی، از
گزینه --tests استفاده کنید:
./gradlew :app:testDebugBackupTestDefaultTestSuite --tests "com.example.myapp.ThemeBackupTest"بررسی گزارشهای آزمایش
پساز تکمیل اجرای آزمایشی، AGP گزارش آزمایشی HTML را در
{module}/build/reports/tests/{taskName}/index.html، مانند
app/build/reports/tests/testDebugBackupTestDefaultTestSuite/index.html تولید میکند.
وقتی آزمایشی ناموفق است، چارچوب آزمایش logcat را از هر دستگاهی که آزمایش استفاده کرده است ذخیره میکند و مسیرهای فایل ذخیرهشده را در خروجی ناموفق فهرست میکند. برای خاموش کردن ذخیره گزارش، -Dandroidx.test.backup.device.logs.enabled=false را رد کنید.
عیبیابی بازیابیهای ناموفق
در موارد زیر، بازیابی ابری یا دستگاهبهدستگاه با پیام خطا ناموفق میشود:
- «خدمات Google Play» برنامه شما را نادیده میگیرد، برای مثال چون شناسه برنامه آن با
com.google.یاcom.android.شروع میشود، پشتیبانگیری آن خالی است، یاandroid:allowBackup="false"را تنظیم میکند. برای آزمایش کردن برنامهای با چنین شناسه برنامهای، از حالتLOCALاستفاده کنید. - نسخه خدمات Google Play پایینتر از ۲۶.۳۳.۳۰ در دستگاه یا شبیهسازی باشد که سطح میانای برنامهسازی کاربردی ۳۶.۱ یا بالاتر را اجرا میکند. «خدمات Google Play» را ازطریق «فروشگاه Play» در دستگاه بهروز کنید، یا از ایمیج سیستم همراه با نسخه ۲۶.۳۳.۳۰ یا بالاتر استفاده کنید.
مشکلات و محدودیتهای شناختهشده
آزمایش پشتیبانگیری و بازیابی خودکار مشکلات و محدودیتهای شناختهشده زیر را دارد:
- پشتیبانی Android Studio از مجموعههای آزمون هنوز دردسترس نیست. ویرایشگر کد ممکن است خطاهای مرجع حلنشده را در فایلهای منبع مجموعه آزمون نشان دهد، حتی زمانی که پروژه شما بهدرستی پیکربندی شده باشد. درعوض، آزمایشهای پشتیبانگیری و بازیابی را از خط فرمان بااستفاده از Gradle بسازید و اجرا کنید.
- آزمایشها فقط در دستگاهها و شبیهسازهایی اجرا میشوند که ازطریق adb به ماشین توسعه نرمافزار شما متصل هستند. از Firebase Test Lab پشتیبانی نمیشود.
- آزمایشها دادههای برنامهتان را پاک میکنند و تنظیمات پشتیبانگیری را در دستگاههایی که استفاده میکنند تغییر میدهند. توصیه میکنیم از دستگاههای آزمایشی اختصاصی استفاده کنید.
برای تغییرات در هر نسخه پخش، یادداشتهای انتشار آزمایش پشتیبانگیری و بازیابی را ببینید.