Тестування мобільних додатків: XCTest, Espresso, Detox та Appium
Тест, який падає на CI раз в п'ять запусків без відтворюваної причини, гірший за його відсутність. Flaky tests — головна причина, чому команди перестають довіряти тестовій інфраструктурі та відключають її.
Unit-тесты: Що Тестувати, а Що Ні
На iOS XCTest — основа. Бізнес-логіка в ViewModel, Interactor, UseCase — тестується без проблем якщо вона не витягує UIKit. Типова помилка: логіка в UIViewController безпосередньо — тоді unit-тест потребує створення view ієрархії, що повільно та нестабільно.
Для асинхронного коду у Swift: XCTestExpectation для старого стилю, await + XCTest async для сучасного. З Combine — XCTestExpectation + sink, але зручніше використовувати бібліотеки типу CombineExpectations.
На Android JUnit 4/5 + Mockito для unit-тестів, Coroutines Test для suspend-функцій. runTest {} з kotlinx-coroutines-test — стандарт для тестування ViewModel з StateFlow.
UI-тесты: Стабільність Важливіше за Покриття
XCUITest (iOS) та Espresso (Android) — нативні UI-тести. Працюють швидко, інтегровані з IDE, але тестують одну платформу.
Головна проблема XCUITest — крихкість селекторів. app.buttons["Увійти"] падає при зміні локалізації або рефакторингу accessibility label. Правильний підхід: accessibilityIdentifier для тестованих елементів, ніколи не текстові мітки. Ідентифікатори з shared enum — щоб вони не розходилися між додатком та тестами.
Espresso на Android стабільніше завдяки механізму IdlingResource — тест автоматично чекає завершення background операцій. Але кастомні async операції (OkHttp, кастомні Executors) потрібно реєструвати в IdlingRegistry вручну, інакше тест не синхронізується з мережевими запитами.
Detox та Patrol: End-to-End для React Native та Flutter
Detox — E2E фреймворк для React Native, розроблений Wix. Працює на реальних пристроях та симуляторах через Gray Box підхід: знає про стан JS thread та синхронізується з ним. Це вирішує головне джерело flakiness — тест не натискає кнопку поки додаток зайнятий.
Налаштування Detox нетривіально. Потребує спеціальної debug-збірки з DetoxInstrumentsServer, конфігурації в package.json та окремого Appium-сервера не потрібно. Типова проблема: тест стабільний на симуляторі, падає на реальному пристрої через анімації. Рішення — animations: disabled у конфігурації Detox для E2E збірки.
Patrol — аналог для Flutter. Розширює вбудований пакет integration_test та додає можливість взаємодіяти з нативними системними діалогами (permission prompts, notifications) — те, що flutter_driver та базовий integration_test не вміють. Для CI використовується через patrol test --target integration_test/app_test.dart.
Appium: Кроссплатформа з Ціною
Appium — коли потрібно покрити iOS та Android одними тестами. Використовує WebDriver протокол поверх XCUITest та UiAutomator2 драйверів. Швидкість нижче нативних фреймворків, але для команд без ресурсів на дві тестові кодові бази — компроміс.
Appium 2.x з архітектурою плагінів помітно зручніший за першу версію. appium-doctor діагностує окруження — корисно при налаштуванні CI.
CI та Паралелізація
Для паралельного запуску XCUITest використовуємо Xcode Cloud або xcodebuild test-without-building з кількома симуляторами через parallel-testing-enabled. Час прогону 200 UI-тестів з паралелізацією на 4 симулятори — з 40 хвилин до 12.
| Фреймворк | Платформа | Gray Box | Швидкість | Системні діалоги |
|---|---|---|---|---|
| XCUITest | iOS | Ні | Висока | Так (через addUIInterruptionMonitor) |
| Espresso | Android | Так (IdlingResource) | Висока | Обмежено |
| Detox | React Native | Так | Середня | Обмежено |
| Patrol | Flutter | Частково | Середня | Так |
| Appium | iOS + Android | Ні | Низька | Так |
Терміни: налаштування тестової інфраструктури з нуля (CI, unit + UI тести, звіти) — 2-3 тижні. Написання покриття для існуючого додатку — індивідуально від обсягу та стану кодової бази.







