Снепшот-тестування — це метод автоматизованої перевірки користувацького інтерфейсу, за якого поточний стан екрана порівнюється з еталонним зображенням (снепшотом), збереженим на попередньому прогоні тесту. Будь-яка візуальна розбіжність фіксується як зміна, що потребує підтвердження розробника. На відміну від UI-тестів, які перевіряють наявність елементів, снепшот-тести фіксують піксельні зміни — зсуви, колірні відхилення та порушення верстки. За даними Android Developers, 2024, снепшот-тестування виявляє до 30% візуальних регресій, пропущених традиційними UI-тестами, що робить його незамінним інструментом для підтримки консистентного інтерфейсу.
Головне
Снепшот-тестування (snapshot testing) — це методика, за якої тест рендерить компонент інтерфейсу, зберігає отримане зображення як еталон і при наступних запусках порівнює поточний рендер з цим еталоном. Якщо зображення збігаються — тест пройдено. Якщо знайдено відмінності — тест падає, а розробник отримує diff-зображення з підсвічуванням змінених пікселів. Техніку запозичено з веброзробки (Jest snapshots) і адаптовано для мобільних платформ.
Головна цінність снепшот-тестів — автоматичне виявлення неочікуваних візуальних змін. Розробник може змінити колірну схему в глобальній темі й випадково вплинути на десяток екранів. UI-тести, що перевіряють наявність кнопок і текстів, цього не помітять. Снепшот-тест зафіксує зміну кожного пікселя на кожному порушеному екрані, надавши повну картину впливу зміни.
Згідно з опитуванням Mobile DevOps Summit 2023, команди, що використовують снепшот-тестування на додачу до класичних UI-тестів, скорочують кількість візуальних дефектів у релізах на 40%. Особливо ефективний цей підхід у проєктах з дизайн-системами та компонентними підходами, де зміна одного базового компонента може зачепити десятки екранів застосунку.
Принципова відмінність — в об'єкті перевірки. UI-тести перевіряють наявність, стан і поведінку елементів інтерфейсу: «кнопка видима», «текст містить повідомлення про помилку», «після натискання відкривається новий екран». Снепшот-тести перевіряють зовнішній вигляд цілком: розташування елементів, відступи, кольори, шрифти, тіні та округлення. Снепшот-тест відповідає на запитання «чи виглядає екран так, як очікується?», а UI-тест — «чи працює екран так, як очікується?»
Швидкість виконання також відрізняється. UI-тести запускаються на емуляторі або реальному пристрої, потребують повного завантаження застосунку й займають від 10 секунд до хвилини на один сценарій. Снепшот-тести на базі бібліотек на кшталт Paparazzi рендерять компонент у віртуальному середовищі без запуску емулятора, що скорочує час тесту до 100–500 мілісекунд. Повний набір снепшот-тестів (50–100 екранів) виконується за 2–5 хвилин замість 30–60 хвилин для аналогічного набору UI-тестів.
Однак снепшот-тести не замінюють UI-тести. Оптимальна стратегія — комбінація: снепшот-тести покривають візуальну регресію (рендер кожного екрана в базових станах), а UI-тести — поведінкову (клік-сценарії, валідацію введення, навігацію). Таке поєднання дає 90% впевненості в коректності інтерфейсу за мінімального часу CI-прогону.
На Android основними інструментами є Paparazzi та Shot. Paparazzi від Cash App рендерить компоненти в тестовому середовищі на JVM без емулятора, використовуючи гравітаційну розкладку Layoutlib. Shot від Karumi виконує Instrumentation-скріншоти на реальному пристрої або емуляторі й порівнює їх з еталонами через бібліотеку AShot, враховуючи відмінності в роздільній здатності та щільності пікселів.
Paparazzi не потребує запуску емулятора — рендер виконується на JVM через Layoutlib, що дає швидкість, порівнянну з модульними тестами. Бібліотека підтримує як View-систему, так і Jetpack Compose. Для Compose використовується модифікатор paparazzi.snapshot { MyComposable() }. Еталони зберігаються в src/test/snapshots і автоматично порівнюються при кожному запуску. Максимальний відсоток різниці налаштовується через maxPercentDifference.
SnapshotTesting від Point-Free підтримує порівняння не лише UIImage, а й рядків, JSON, Data та цілих Core Data-сторів. Це робить її універсальним інструментом не тільки для UI-снепшотів, але й для перевірки серіалізації та декодування JSON-відповідей. Для SwiftUI використовується розширення assertSnapshot з модифікатором .image(on: .iPhone13). Стратегія record: true — створює еталони при першому запуску.
Для React Native популярне рішення react-native-testing-library у поєднанні з jest-image-snapshot. Вебпідхід до снепшот-тестування переноситься в мобільне середовище через рендер компонентів у Node.js середовищі з подальшим порівнянням JSON-зліпків віртуального DOM. Цей підхід швидший за нативний, але менш точний — він не враховує платформні особливості рендеру шрифтів і системних компонентів. Для Flutter використовується golden-тестування через goldens toolkit.
Розглянемо снепшот-тести для Android (Paparazzi) та iOS (SnapshotTesting). Обидва приклади перевіряють зовнішній вигляд компонента — картки користувача з аватаром, іменем і статусом. Тест рендерить компонент з тестовими даними й порівнює результат з еталонним зображенням, збереженим у репозиторії.
Paparazzi використовує анотацію @Test та метод snapshot() для захоплення рендеру. Еталони зберігаються в папці src/test/snapshots і автоматично підтягуються при наступному запуску для порівняння.
class UserCardSnapshotTest {
@get:Rule
val paparazzi = Paparazzi(
theme = "Theme.MyApp",
maxPercentDifference = 0.1
)
@Test
fun userCard_defaultState() {
val card = UserCard(
name = "Alice Johnson",
status = "Online",
avatarUrl = "https://example.com/avatar.png"
)
paparazzi.snapshot(card)
}
@Test
fun userCard_offlineState() {
val card = UserCard(
name = "Bob Smith",
status = "Offline",
avatarUrl = null
)
paparazzi.snapshot(card, name = "user_card_offline")
}
}
SnapshotTesting використовує модифікатор .snapshot() всередині assertSnapshot. Бібліотека автоматично визначає формат — UIImage для UIView, String для тексту, Data для бінарних даних.
import SnapshotTesting
import XCTest
class UserCardSnapshotTests: XCTestCase {
func testUserCardDefaultState() {
let card = UserCardView(
name: "Alice Johnson",
status: "Online",
avatarURL: URL(string: "https://example.com/avatar.png")
)
let controller = UIHostingController(rootView: card)
assertSnapshot(matching: controller, as: .image(on: .iPhone13))
}
func testUserCardOfflineState() {
let card = UserCardView(
name: "Bob Smith",
status: "Offline",
avatarURL: nil
)
assertSnapshot(matching: card, as: .image(on: .iPhone13))
}
}
Типовий робочий процес включає чотири етапи. Перший запуск (record mode): всі снепшот-тести виконуються в режимі запису — еталонні зображення створюються та зберігаються в репозиторій. Цей етап виконується при первинному налаштуванні тестів або після усвідомленої зміни інтерфейсу. Після запису еталони комітяться разом з кодом — вони стають частиною проєкту.
При наступних запусках тести працюють у режимі порівняння: кожен новий рендер порівнюється з еталоном. Якщо знайдено відмінності, генерується diff-зображення: зеленим підсвічуються пікселі, що збігаються з еталоном, червоним — відмінні. Розробник вивчає diff і приймає рішення: якщо зміна очікувана (свідома зміна дизайну), еталон оновлюється командою record; якщо неочікувана — фіксується баг. Оновлення еталонів виконується разовою командою: для Paparazzi це `./gradlew recordPaparazzi`, для SnapshotTesting — `assertSnapshot(record: true)`.
За даними Spotify Engineering Blog (2022), команди, що використовують описаний workflow, витрачають на аналіз diff-зображень у середньому 2 хвилини на тест. При наборі з 50 снепшот-тестів повний цикл оновлення еталонів займає 15–20 хвилин, що значно швидше за ручну верифікацію візуальних змін на 50 екранах.
Снепшот-тести мають принципові обмеження. Чутливість до середовища: один і той самий компонент може відрендеритися по-різному на різних версіях ОС, щільностях екрана та шрифтових конфігураціях. Еталони, створені на одній машині, можуть відрізнятися від рендеру на CI-сервері. Рішення — використовувати фіксовані параметри середовища: конкретну версію Layoutlib для Paparazzi або точну модель пристрою для SnapshotTesting.
Антипатерн №1: гігантські снепшоти — снепшот-тест, що захоплює весь екран цілком, падає при кожній мінімальній зміні будь-якого компонента. Правильний підхід — тестувати окремі компоненти (кнопку, картку, поле введення) ізольовано. Кожен компонент тестується незалежно, що дає точне вказування на джерело зміни. Антипатерн №2: ігнорування diff-ів — автоматичне оновлення еталонів без аналізу diff-зображень зводить цінність снепшот-тестів нанівець. Кожен diff потребує свідомого рішення розробника.
За даними Better Engineering Blog (2023), снепшот-тести приносять найбільшу користь при покритті компонентів дизайн-системи та ключових екранів у базових станах — порожньому, заповненому, помилковому та граничному. Покриття анімацій та динамічних станів через снепшот-тести неефективне через недетермінованість часових міток у рендері — для таких сценаріїв краще підходять відеозапис або ручна QA-перевірка.
Часті запитання
Ні, снепшот-тести перевіряють зовнішній вигляд, а UI-тести — поведінку інтерфейсу. Оптимальна стратегія — комбінувати обидва підходи: снепшоти для візуальної регресії, UI-тести для перевірки сценаріїв і навігації. Снепшоти відповідають на питання «чи виглядає правильно», UI-тести — «чи працює правильно».
Еталони оновлюються при кожній усвідомленій зміні дизайну: новий колір теми, змінені відступи, додавання або видалення елементів. Оновлення виконується через record-режим, після чого diff-зображення перевіряються на code review, щоб переконатися, що зміни відповідають очікуванням дизайнера.
У першу чергу компоненти дизайн-системи — кнопки, картки, поля введення, модальні вікна. Потім ключові екрани в базових станах. Не тестуйте снепшотами анімації, WebView, мапи та екрани з динамічним контентом — для них снепшоти дають хибні падіння через недетермінованість.
Використовуйте один і той самий API Level для record і test режимів. Для Paparazzi задайте конкретну версію Layoutlib у конфігурації. Для SnapshotTesting фіксуйте модель пристрою. Еталони, створені на Android 14, можуть відрізнятися від рендеру на Android 12 через зміни в системних шрифтах і темі Material.
У CI снепшот-тести запускаються в режимі перевірки (verify). Якщо тест падає, CI показує diff-зображення в артефактах збірки. Record-режим (оновлення еталонів) виконується локально розробником або в окремому CI-завданні з ручним тригером. Еталонні зображення обов'язково комітяться в репозиторій.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також