Статьи
Две стратегии скриншот-тестирования в мобильных проектах
Контекст
Говоря о мобильных проектах или продуктах, особенно зрелых и крупных, тесты скриншотов — это любопытный пример проверок, которые мы хотим сместить влево в жизненном цикле разработки программного обеспечения.
Сообщество iOS-разработчиков уже несколько лет пользуется преимуществами масштабных тестов скриншотов благодаря iOS-симуляторам, безболезненно доступным в системах непрерывной интеграции, и быстрой обратной связи, которую такие тесты обеспечивают на локальной рабочей станции.
С другой стороны, работа Android-эмуляторов в системах непрерывной интеграции может оказаться сложной задачей даже сегодня. Этот факт, безусловно, способствовал плохому внедрению тестов скриншотов для крупных Android-проектов на протяжении многих лет и, следовательно, потребности в большем количестве решений в этой области. Лишь недавно появилось несколько альтернатив, созданных сообществом, и они начали набирать обороты.
В следующей статье я поделюсь более сфокусированной информацией о современных решениях для тестирования снимков экрана в экосистеме Android. Сегодня я напишу о двух стратегиях, которые команда может принять и использовать в отношении этой практики тестирования, указав на некоторые различия между ними.
Оба подхода не зависят от экосистемы и имеют общую точку принятия решения: наличие или отсутствие реализации Дизайн-системы, которую инженеры могут использовать для более быстрой доставки функций.
Понимание тестов скриншотов в мобильных проектах
Продукты, которые общаются с пользователями через мобильное приложение, сталкиваются с тремя ужасными ограничениями по сравнению с тем, что распространяется исключительно через Интернет:
- Магазины приложений продвигают контроль доступа и связанные с ними процессы проверки приложений.
- Когда выходит новая версия продукта для мобильных устройств, это не означает, что пользователь ее установит.
- Невозможно вернуться к предыдущей версии после того, как пользователь установит приложение или его обновление.
Все три пункта способствуют искаженному воплощению непрерывной доставки в контексте мобильных программных продуктов. Эти моменты также напрямую влияют на некоторые инженерные методы в таких проектах.
Например, принятие фиче-флагов для мобильных продуктов в масштабе становится непреложным вариантом. Кроме того, необходимо еще много работы в отношении проверок, которые мы хотим запускать во время непрерывной интеграции, чтобы выявлять ошибки до того, как они попадут к пользователям.
Скриншот-тесты предлагают систему безопасности для обнаружения и предотвращения определенного класса ошибок пользовательского интерфейса. Они не могут предотвратить ошибки, связанные с конкретным взаимодействием пользователя с пользовательским интерфейсом, но они могут гарантировать, что, как минимум, мы не выпустим сломанный экран.
В этом контексте тестирование снимков экрана соответствует нефункциональным тестам, которые мы хотим видеть в наборе тестов. Скорее всего, они не предотвратят нарушение функциональности в конечном продукте, но они дают гарантии, которые помогут нам обеспечить желаемый пользовательский опыт.
Инструментарий для тестирования скриншотов обычно работает поверх среды тестирования, которая может эффективно рендерить пользовательские интерфейсы (например, Android/Instrumentation). Таким образом, тест использует View в качестве объекта тестирования и принимает набор атрибутов представления в качестве входных данных.
Инструмент в конечном итоге использует механизм сопоставления изображений для сравнения визуализированного представления с ожидаемым результатом, так определяя результат теста.
Обзор тестов скриншотов необходим, поскольку я рассматриваю такие тесты как суженные интеграционные тесты, нацеленные на инфраструктуру пользовательского интерфейса. Таким образом, хорошо продуманный тест скриншотов всегда должен проверять набор элементов пользовательского интерфейса изолированно. Использование тестов скриншотов в качестве компонентных тестов, на мой взгляд, является анти-паттерном, поэтому о нем нужно знать и избегать. Я вернусь к этой идее позже в этой статье.
Скриншот-тестирование при отсутствии Дизайн-системы
Когда у команды нет дизайн-системы с программируемыми компонентами, разумный способ внедрить тестирование скриншотов — взять весь экран в качестве тестируемого объекта, таким образом, структурируя тестовые примеры в соответствии с восприятием приложения пользователем в данный момент времени.
Атрибуты View будут разделять действительные состояния экрана для каждого рассматриваемого примера. Обычно мы хотим тестировать такие состояния, как Loading-Content-Error, но в конечном итоге для тестирования могут подойти и другие условия, особенно в сложных интерфейсах.
Кроме того, у каждого состояния могут быть варианты, в которых нам нужны гарантии правильной работы пользовательского интерфейса. Например, на следующем изображении показано, как мы можем исследовать сценарий обновления результатов с точки зрения нагрузки на пользовательский интерфейс:
Следуя этой стратегии, легко понять, что базовый код скриншот-тестов масштабируется в соответствии с количеством экранов, подходящих для тестирования. В пределе, чтобы иметь полный охват, нам нужно учитывать все экраны, которые мы поставляем в продукте, все возможные состояния для каждого экрана и все возможные варианты в каждом состоянии.
На основе такого сценария рассмотрим приложение, состоящее из следующего:
- 100 экранов
- 3 тестируемыми состояниями на экран (в среднем) и
- 3 вариантами каждого тестируемого состояния (в среднем)
Основываясь на этих цифрах, мы ожидаем, что около 900 тестов определят надежную систему защиты от визуальных регрессий для такого продукта.
Скриншот-тестирование при наличии Дизайн-системы
Ради примера давайте рассмотрим сценарий, в котором есть только одна централизованная система проектирования, которую инженеры используют для ускорения разработки функций.
Независимо от внедрения дизайн-системы во всех реализованных функциях и независимо от расширения экранных компонентов, охватываемых ею, для каждого определенного элемента мы можем определить варианты на основе различных атрибутов, которые визуально подчеркивают этот элемент:
Следуя этой стратегии, легко понять, что базовый код скриншот-тестирования, масштабируется в соответствии с количеством компонентов в системе проектирования. В пределе дизайн-система управляет 100% пользовательского интерфейса продукта, а это означает, что каждый экран определяет все его UI-элементы поверх базовых компонентов.
На основе такого сценария рассмотрим дизайн-систему, которая определяет:
- 50 программных компонентов с
- 10 тестируемыми вариантами на компонент (в среднем)
Следует отметить, что в этом примере количество тестируемых вариантов для компонента значительно больше, чем количество вариантов для состояния экрана из предыдущего примера.
Но в такой ситуации должно хватить около 500 тестов, чтобы определить набор, который будет борться с обычными регрессиями пользовательского интерфейса. В конце концов, мы также хотели бы добавить некоторые тесты, охватывающие типичные комбинации компонентов в наборе тестов, выполняя их в агрегированном виде.
Будем считать, что мы добавляем еще 100 тестов для таких комбинаций. У нас получится около 600 кейсов в нашем наборе тестов, что примерно на 33% меньше тестов, чем в предыдущей стратегии, где не было никакой системы дизайна.
Инсайты из реальных проектов
Если вы думаете, что я лишь играл с числами в предыдущих примерах, вы будете удивлены, узнав, что они близки к реальным проектам, которые поставляют продукты, ежедневно обслуживающие сотни тысяч пользователей.
Пообщавшись с несколькими друзьями в отрасли и собрав некоторые данные, а также рассмотрев проекты, над которыми я работал в предыдущие годы, не будет преувеличением поделиться следующими фактами:
- Полнофункциональная Дизайн-система может определять от 40 до 100 программируемых компонентов.
- Сложный мобильный продукт может содержать от 150 до 300 экранов.
- Тестируемые состояния могут взрываться вариациями, обусловленными состоянием ОС (например, светлый/темный режим).
- Практически невозможно, чтобы пользовательский интерфейс продукта на 100% управлялся централизованной системой дизайна.
Учитывая сложную природу, которую принимают мобильные проекты в масштабе, становится ясно, что в долгосрочной перспективе невозможно придерживаться единой стратегии тестирования скриншотов для всего продукта. Довольно часто специализированные группы реализуют нестандартные компоненты из-за бизнес-требований (например, настраиваемый график, настраиваемые поля формы и т. д.).
Как я показал, нам не нужны тесты скриншотов для сценария, где у нас есть экраны, полностью управляемые дизайн-системой, скорее всего «низкоуровневые скриншот-тесты» уже предотвращают те же визуальные ошибки, которые мы хотим отловить.
С другой стороны, для фич, требующих реализации “кастомного” пользовательского интерфейса, в котором элементы не определяются Дизайн-системой, могут быть полезны некоторые скриншот-тесты для всего состояния экрана.
Тем не менее, основной вывод из оценки двух стратегий по-прежнему актуален даже при смешанном подходе. Когда присутствует дизайн-система, основное внимание следует уделять тестам снимков экрана, которые фиксируют визуальные регрессии на уровне элементов, подталкивая проект и продукт к тому, чтобы со временем использовать преимущества существующих компонентов.
Неправильный подход — не использовать преимущества существующей Дизайн-системы (даже если она находится на ранних стадиях) и сосредоточиться в первую очередь на тестах скриншотов на уровне функций.
Я видел это в прошлом, по крайней мере, в двух разных контекстах. В первом инженеры исторически использовали тесты скриншотов как тесты компонентов на уровне экрана; следовательно, не было никакого стимула прикладывать усилия к использованию отдельных элементов системы дизайна.
Во втором случае практика тестирования скриншотов появилась в проекте поздно, когда реализация дизайн-системы уже была испытана в боевых условиях и получила широкое распространение. На этом этапе большинство инженеров не смогли увидеть, как реализация тестовых случаев со скриншотами за пределами их функций обеспечивает общую ценность, которую они хотят достичь. Поэтому все равно реализовали тестовые примеры со скриншотами на уровне функций.
Результат второй ситуации должен быть легко понятен: набор тестов скриншотов начал расти, но было непрактично смещать визуальные тесты влево, поскольку скриншоты из разных функций накладывались друг на друга. Их экраны были построены поверх одних и тех же компонентов, но нацелены только на тривиальные состояния пользовательского интерфейса Content-Empty-Error.
Заключительные замечания
Масштаб продукта напрямую отражается в количестве фич, которые мы предоставляем пользователям, то есть в расширении пользовательского интерфейса. Скриншот-тестирование — это инструмент для обнаружения визуальных регрессий в UI, которым мы занимаемся во время цикла разработки.
Инвестирование в скриншот-тесты (в первую очередь через дизайн-систему) позволяет проекту ловить больше визуальных регрессий в наборе тестов при одновременном сокращении количества интеграционных тестов в долгосрочной перспективе.
В качестве дополнительного преимущества мы предоставляем проекту лучшие возможности для рефакторинга реализации существующих компонентов, например, при переходе от старого к новому инструментарию пользовательского интерфейса, что важно для многих крупных проектов, которые не могут обойти такие технологии, как Jetpack Compose или Swift UI.
Кроме того, инженеры должны обращать внимание на нефункциональный характер тестирования снимков экрана и воздерживаться от использования этого метода в широких интеграционных тестах. Последнее, что нам нужно, — это ненадежный нефункциональный набор тестов.
Если вы прочитали это, спасибо за ваше время!