Тестирование работы мобильного приложения в офлайн-режиме
Офлайн-режим — одна из тех функций, которую заявляют в требованиях и тестируют поверхностно. «Без интернета не работает» — и окей. Но для приложений, которыми пользуются в метро, в поездках, в зонах слабого покрытия, это критично. Хуже «не работает» — работает неправильно: показывает устаревшие данные без пометки, теряет введённые данные, молча ошибается.
Что проверяем
Три основных сценария:
Запуск без сети. Что видит пользователь, открывая приложение без интернета? Правильно: последние кэшированные данные с меткой времени («обновлено 2 часа назад»). Неправильно: белый экран или сообщение «нет соединения» без какого-либо контента.
Потеря соединения во время использования. Пользователь просматривал ленту, соединение исчезло. Уже загруженный контент должен остаться доступным. Незаконченные действия — сохранены как черновики или поставлены в очередь.
Восстановление соединения. Данные синхронизируются, очередь отложенных действий выполняется, конфликты разрешаются. Пользователь видит актуальные данные без ручного обновления.
Локальное кэширование: что и как
На iOS — NSURLCache для HTTP-ответов (если сервер возвращает правильные Cache-Control заголовки), Core Data или Realm для структурированных данных, UserDefaults для небольших настроек. Для медиа — FileManager с собственным каталогом кэша.
Core Data с NSPersistentCloudKitContainer даёт синхронизацию через CloudKit «из коробки», но конфликты разрешает примитивно — last-write-wins. Для сложной логики конфликтов реализуем кастомный merge policy.
На Android — Room для структурированных данных, DataStore для настроек, Cache-Control через OkHttp для HTTP-кэша:
val cacheSize = 10 * 1024 * 1024L // 10 MB
val cache = Cache(context.cacheDir, cacheSize)
val okHttpClient = OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control", "public, max-age=300") // 5 минут
.build()
}
.addInterceptor { chain ->
val request = if (isNetworkAvailable()) {
chain.request()
} else {
chain.request().newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=${60 * 60 * 24}") // 24 часа из кэша
.build()
}
chain.proceed(request)
}
.build()
only-if-cached + max-stale — читаем из кэша, даже если данные устарели, пока офлайн.
Очередь отложенных действий
Действия пользователя в офлайне должны выполниться при восстановлении сети. Не «потеряться с ошибкой», а сохраниться и выполниться.
Android — WorkManager с setRequiredNetworkType(NetworkType.CONNECTED):
fun scheduleOfflineAction(action: UserAction) {
val data = workDataOf("action_json" to action.toJson())
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setInputData(data)
.build()
WorkManager.getInstance(context).enqueue(request)
}
Действие сохраняется в базу WorkManager, выполняется как только появится сеть, переживёт перезагрузку устройства.
iOS — BackgroundTasks framework (BGProcessingTask) или более простой вариант: локальная очередь в Core Data, попытка выполнения при applicationDidBecomeActive и при изменении сети через NWPathMonitor.
Тестовые сценарии и инструменты
Отключение сети в тесте:
iOS симулятор: Hardware → Network Link Conditioner → 100% Loss или xcrun simctl status_bar + эмуляция через Network Link Conditioner profile.
Android эмулятор: adb shell svc wifi disable && adb shell svc data disable. Восстановление: enable.
В автотестах (Detox):
it('shows cached data when offline', async () => {
// Загружаем данные при наличии сети
await element(by.id('feed_list')).waitForVisible();
// Отключаем сеть (только на Android через adb)
await device.setNetworkConditions({ offline: true });
// Перезапускаем и проверяем кэш
await device.reloadReactNative();
await expect(element(by.id('cached_banner'))).toBeVisible();
await expect(element(by.id('feed_list'))).toBeVisible(); // кэш работает
});
Конфликты при синхронизации
Пользователь отредактировал запись офлайн. Другой пользователь изменил ту же запись онлайн. При восстановлении соединения — конфликт.
Стратегии: last-write-wins (проще, но теряет данные), server-wins (предсказуемо, но игнорирует офлайн-правки), merge (правильно, но сложно). Для большинства приложений достаточно показать пользователю диалог выбора версии.
Сроки
2–3 дня — тестирование по чеклисту офлайн-сценариев, оценка корректности кэширования и очереди действий, отчёт. Стоимость рассчитывается индивидуально.







