Создайте правильный процесс проверки для библиотек пользовательского интерфейса Android с помощью тестирования моментальных снимков.

Тестирование должно быть самой важной вещью, о которой думает разработчик, когда пишет (или пытается написать) поставляемый код.

Однако независимо от того, насколько это важно, тестирование иногда может быть трудным по своей сути.

Это особенно верно для библиотек пользовательского интерфейса Android, где такие вещи, как модульное тестирование или тестирование пользовательского интерфейса (с использованием Espressο, UiAutomator), начинают иметь меньше смысла.

Мой вариант использования

Проблема, которую мне пришлось решить, заключалась в попытке протестировать мою библиотеку StageStepBar.

По сути, это индикатор выполнения, который может содержать этапы (вехи) и отдельные шаги между ними. Он также легко настраивается, что позволяет эффективно изменять внешний вид и поведение всех его компонентов. Это действительно большое пользовательское представление, большая часть кода которого находится внутри файла onDraw.

Все казалось прекрасным и денди, пока я не понял, что способ, которым я мог проверить это после замены материала, был очень громоздким. К счастью, я включил пример приложения, но мне, по сути, пришлось модифицировать, собрать и запустить приложение, чтобы убедиться, что мои изменения правильно влияют на библиотеку. Но даже этот процесс не был достаточно пуленепробиваемым, поскольку:

  • Иногда я забывал перепроверить некоторые случаи.
  • В других случаях я не стал бы тестировать некоторые случаи, которые кажутся несущественными, но на которые «таинственным образом влияют».
  • Во всем этом по-прежнему задействовано одно устройство. Что происходит с разными? Локали RTL? Список можно продолжить…

Введите тестирование моментальных снимков

Во время посещения Droidcon 2021 в Лондоне я познакомился с другим способом тестирования вещей в мире Android — тестированием снимков. Вот хороший разговор на эту тему.

Суть в следующем:

  • Вы делаете кучу скриншотов и/или видео вашего пользовательского интерфейса.
  • Если вы довольны ими, вы храните их где-то в своем репо как золотые ценности/источники правды.
  • Если вносятся какие-либо изменения, алгоритм проверки (обычное сравнение пикселей за пикселем) проходит через ваш код и сравнивает золотые значения с новыми снимками экрана.
  • Если эти изменения ожидаются/желательны, скриншоты будут заменены. В противном случае код нарушает некоторые вещи, которые не должен.

Это казалось правильным подходом для моего варианта использования. Тем не менее, конкретная библиотека, которая была продемонстрирована на этом мероприятии под названием «Папарацци», действительно убедила меня в том, что это так.

Что такое папарацци

Это еще одна библиотека для тестирования моментальных снимков, но отличие от других доступных на данный момент решений (см., например, Shot) заключается в том, что она полностью работает на JVM без необходимости использования устройства или эмулятора.

Этот доклад с того же Droidcon идет более подробно о том, как это работает.

https://github.com/cashapp/paparazzi

Это было очень удобно для моего варианта использования, поскольку это означало, что я мог использовать Github Actions, чтобы создать способ как для меня, так и для любых будущих участников, чтобы получить быструю обратную связь о том, сломали ли их изменения что-то.

Стоит отметить, что на момент написания (12 февраля 2022 г.) библиотека еще не была стабильной. Полная документация отсутствует, и кажется, что над ней все еще работают. Так что может случиться так, что то, что вы видите здесь, может измениться через несколько месяцев. Также на момент написания статьи не было поддержки Compose.

Итак, со всем этим, вот полный процесс, которому я следовал:

Написание тестов (процесс записи)

Прежде всего, я создал простой библиотечный модуль. Этот модуль:

  • Зависит от модуля StageStepBar (который тестируется).
  • Применен ли к нему плагин Paparazzi. Это дает ему запись и проверяет задачи Gradle, которые мы будем использовать позже.
  • Содержит тесты и золотые значения (скриншоты) под src/test и src/test/snapshots соответственно.

Пример тестов, которые мы можем написать, следующий:

По сути, у нас есть файл макета с представлением, которое мы хотим протестировать. Папарацци раздувают его своим особым контекстом, используя paparazzi.inflate().

Затем мы можем получить доступ к этому представлению и перевести его в состояние, в котором мы хотим, чтобы оно находилось для каждого конкретного теста.

Затем вызов paparazzi.snapshot() закрывает сделку, поскольку он отображает первый кадр всего, что мы настроили, в файл скриншота. Если нам нужно больше, чем просто кадр (скажем, мы тестируем анимацию), то мы можем использовать удобный метод paparazzi.gif(). Результатом после запуска gradle module-name:testDebug на этом модуле являются эти два снимка экрана:

Все эти файлы снимков теперь находятся в папке build/. Если мы довольны тем, как все выглядит, мы можем просто запустить:

gradle module-name:recordPaparazziDebug

Это также поместит эти файлы в src/test/snapshots, что сделает их доступными для проверки с помощью git. Теперь наш источник правды создан!

Примечание. Стоит отметить, что авторы папарацци настоятельно рекомендуют использовать LFS для этих файлов. Поскольку скриншоты будут меняться в будущем, история git будет заполнена этими бинарными блобами. LFS позволяет вместо этого сохранять указатели, которые будут указывать на удаленные файлы на другом сервере, что упростит и ускорит весь процесс для вас. Документы Paparazzi и Github содержат руководство по установке.

Тестирование изменений кода (Процесс проверки)

После того, как все вышеперечисленное было добавлено в репозиторий, пришло время подумать о том, как теперь эти вещи будут применяться. Поэтому для этого я создал рабочий процесс действий GitHub, который для каждого нажатия на открытый PR:

  • Запустите задачу gradle module-name:verifyPaparazziDebug, чтобы сравнить снимки экрана, которые выводит PR-код, с золотыми значениями.
  • Если какой-либо из тестов не пройден, то изображение с ожидаемыми/реальными снимками экрана вместе с их дельтой будет загружено в качестве артефакта в этом прогоне (за непройденный тест). Он также поместит комментарий к PR, который укажет разработчику на раздел артефактов, чтобы они могли изучить ошибки. Дельта — это действительно приятное дополнение, которое мы получаем от библиотеки. Вот как это выглядит:

  • Если вместо этого все тесты пройдены, то вместо этого в PR публикуется счастливое сообщение.

Полный рабочий процесс CI выглядит следующим образом:

Но что произойдет, если изменения желательны? В этом случае рецензент (в данном случае я) может одобрить PR и попросить автора запустить задачу записи своего кода и отправить полученные снимки.

Затем, наконец, когда этот PR будет объединен с основной веткой, золотые значения будут обновлены.

Последние мысли

Я действительно считаю, что тестирование снимков с помощью Paparazzi может быть особенно эффективным способом создания надлежащего процесса проверки для библиотек пользовательского интерфейса Android, которые раньше было трудно протестировать. Тот факт, что он работает прямо на JVM, означает отсутствие настройки эмулятора/устройства, быстрое выполнение и более простую интеграцию с CI.

Огромное спасибо людям, которые работают над этой замечательной библиотекой, которую я просто использую!

Мне очень интересно узнать, что вы, люди, думаете обо всем этом!