Как тестируют мобильное приложение Netflix

Контекст

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

Приложение Netflix для Android было создано 14 лет назад. Изначально это было гибридное приложение (native+webview), но потом было преобразовано в полностью нативное — из-за проблем с производительностью и сложностей с созданием пользовательского интерфейса, который бы ощущался и работал как настоящий нативный. 

Как и большинство «старых» мобильных приложений, оно сейчас в процессе преобразования в Jetpack Compose. Текущая кодовая база составляет около 1 млн строк кода на Java и Kotlin, распределенного по 400+ модулям, и, как и в большинстве старых приложений, здесь есть модуль-монолит (поскольку оригинальное приложение представляло собой один большой модуль). Над приложением работает команда из примерно 50 человек.

Одно время существовала специальная мобильная команда разработчиков-SDET (Software Developer Engineer in Test), которая занималась написанием всех тестов для устройств, следуя обычной схеме работы с разработчиками и менеджерами продукта, чтобы понять тестируемые фичи и создать тест-планы для всех автотестов. 

В Netflix SDET были разработчиками, специализирующимися на тестировании; они писали автотесты с помощью Espresso или UIAutomator; также создавали фреймворки для тестирования и интегрировали в процесс сторонние фреймворки. Разработчики фич писали юнит-тесты и тесты Robolectric для своего кода.

Специальная SDET-команда была расформирована несколько лет назад, и теперь автотесты принадлежат каждой из feature-команд; по-прежнему есть 2 «дополняющих» SDET, которые помогают различным командам по мере необходимости. QA, то есть выделенные тестировщики, вручную тестируют релизы перед их выгрузкой, то есть проводят финальный дымовой тест.

В нашем мире медиастримов одной из сложных проблем является огромная экосистема пользовательских устройств. Мы стремимся поддерживать хороший user experience на устройствах с малым объемом памяти или в принципе медленных устройствах (например Android Go), при этом обеспечивая премиальный опыт на более дорогих устройствах.

В случае складных смартфонов некоторые из них не сообщают приложению Netflix о наличии соответствующего датчика шарнира. Мы поддерживаем устройства до версии Android 7.0 (API24), но в ближайшее время мы установим минимальную версию Android 9. Некоторые версии Android, выпускаемые конкретными производителями, также имеют свои «причуды». 

В общем, работа с физическими девайсами составляет большую часть нашего тестирования.

Наша пирамида

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

Netflix testing pyramid

Уровни снизу вверх: модульные, скриншотные И интеграционные, E2E, дымовые

Однако из-за интенсивного тестирования на физических устройствах и большой легаси кодовой базы наша тестовая пирамида в реальности больше похожа на песочные часы или перевернутую пирамиду. А новые фичи Netflix придают пирамиде более типичную форму.

Наше скриншот-тестирование проводится на нескольких уровнях: компонент UI, макет экрана UI, и макет экрана интеграции с девайсом. Первые два — это действительно модульные тесты, так как они не выполняют никаких сетевых вызовов. Последний является заменой большинства ручных тестов.

Фреймворки для юнит-тестов

Юнит-тесты используются для проверки бизнес-логики, не зависящей от поведения устройства/UI. В старых частях приложения мы используем RxJava для асинхронного кода и тестирования стримов. В новых частях приложения для потоков состояний (state flows) используются Kotlin Flows и Composables, которые гораздо проще упорядочить и проверить, по сравнению с RxJava.

Для юнит-тестирования используем следующие фреймворки:

  • Strikt: для ассертов, потому что у него хороший API, как в AssertJ, но написан для Kotlin.
  • Turbine: для недостающих частей в тестировании Kotlin Flows
  • Mockito: для подстановки сложных классов, не относящихся к текущему тестируемому модулю кода
  • Hilt: для подстановки тестовых зависимостей в наш граф инъекции зависимостей
  • Robolectric: для тестирования бизнес-логики, которая должна каким-либо образом взаимодействовать с сервисами/классами Android (например, parcelables или Services).
  • Фреймворк для A/B-тестов и фича-флагов: позволяет переопределить автотест для конкретного A/B-теста или включить/выключить конкретную функцию.

Разработчикам рекомендуется использовать обычные юнит-тесты перед переходом на Hilt или Robolectric, так как время выполнения увеличивается в 10 раз на каждом этапе при переходе от обычных модульных тестов -> Hilt -> Robolectric. Mockito также замедляет сборку при использовании инлайн-моков, поэтому использовать инлайн-моки не рекомендуется. Тесты устройств на несколько порядков медленнее, чем любые из юнит-тестов. А скорость тестирования важна при таких больших кодовых базах.

Нестабильные юнит-тесты

Поскольку юнит-тесты являются блокирующими в нашем CI-конвейере, минимизация нестабильности очень важна. Как правило, есть две причины нестабильности: нежелательное сохранение состояния при переходе к следующему тесту, и тестирование асинхронного кода.

Классы юнит-тестов JVM (Java Virtual Machine) создаются один раз, а затем методы тестов в каждом классе вызываются последовательно; инструментальные тесты, однако, запускаются с самого начала, и единственное время, которое можно сэкономить — это установка APK. В связи с этим, если метод тестирования оставит измененное глобальное состояние в зависимых классах, следующий тестовый метод может завершиться неудачей. Глобальное состояние может быть разным: файлы на диске, базы данных на диске, и общие классы. Использование инъекции зависимостей или пересоздание всего что изменяется, решает эту проблему.

В асинхронном коде всегда возможны сбои, поскольку мультитрединг меняет многие вещи. Диспетчеры тестов (Kotlin Coroutines) или планировщики тестов (RxJava) могут быть использованы для контроля тайминга каждого потока, чтобы сделать ситуацию детерминированной при тестировании определенных условий. Это делает код менее реалистичным и возможно пропустит некоторые тестовые сценарии, но предотвратит нестабильность тестов.

Фреймворки для тестирования скриншотов

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

Мы используем следующие фреймворки для тестирования скриншотов:

  • Paparazzi: для компонентов UI Compose и макетов экрана; сетевые вызовы не могут применяться для загрузки изображений, поэтому вы должны использовать статические ресурсы изображений или загрузчик изображений, который рисует шаблон запрашиваемых изображений (мы делаем и то, и другое).
  • Тестирование скриншотов локализации: создание скриншотов экранов работающего приложения во всех локализациях, чтобы UX-команды могли верифицировать их вручную.
  • Тестирование скриншотов на устройствах: используемое для проверки визуального поведения работающего приложения.

Тестирование доступности в Espresso: это также одна из форм тестирования скриншотов, при которой размеры и цвет визуальных элементов проверяются на доступность; это было для нас проблемой, поскольку наша UX-команда приняла стандарт WCAG 44dp минимального размера точки касания, вместо стандартного 48dp в Android.

Фреймворки тестирования на девайсах

Наконец, у нас есть тесты устройств. Как уже говорилось, они в разы медленнее, чем тесты, которые выполняются на JVM. Они заменяют ручное QA и используются для дымового тестирования общей функциональности приложения.

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

Мы используем следующие фреймворки для тестирования на физических устройствах:

  • Espresso: большинство тестов используют Espresso, который является основным инструментальным фреймворком Android для тестирования пользовательских интерфейсов.
  • PageObject test framework: внутренние экраны написаны в виде PageObjects, которыми тесты могут управлять, чтобы облегчить миграцию с XML-макетов на Compose (см. ниже для более подробной информации)
  • UIAutomator: небольшой набор дымовых тестов использует UIAutomator для тестирования полностью обфусцированного бинарного файла, который будет загружен в аппстор (тесты Release Candidate).
  • Система тестирования производительности: измеряет время загрузки различных экранов, чтобы проверить регрессии.
  • Система захвата/воспроизведения сети: воспроизводит записанные вызовы API, для снижения нестабильности тестов на устройствах
  • Фреймворк для мокинга бэкенда: тесты могут сделать запрос к бэкенду — вернуть определенные результаты; например, наша главная страница содержит контент, который полностью управляется алгоритмами рекомендаций, поэтому тест не может целенаправленно искать определенные тайтлы, если не попросить бэкенд вернуть определенные видео в определенных состояниях (например «готовится к публикации») и строки с определенными названиями (например строку Coming Soon с соответствующими видео).
  • Фреймворк A/B-тестов и фича-флагов: позволяет переопределить автотест для A/B-теста или включить/выключить определенную фичу
  • Фреймворк для тестирования аналитики: используется для проверки последовательности событий аналитики из набора действий на экране; аналитика наиболее подвержена сбоям при изменении экранов, поэтому это очень важно протестировать.

PageObjects и Test Steps

Шаблон проектирования PageObject создавался как веб-шаблон, но применяется и в мобильном тестировании. Он отделяет тестовый код (например нажатие на кнопку Play) от кода специфичного для экрана (например обработка нажатия на кнопку в Espresso). Благодаря этому шаблон позволяет абстрагировать тест от его реализации («думайте об интерфейсах, а не реализации, при написании кода«). При необходимости легко заменить реализацию при переходе от XML-макетов к макетам Jetpack Compose, а сам тест (например тестирование логина) остается прежним.

В дополнение к PageObjects для определения абстракции с экранами, у нас есть концепция «Тестовые Шаги». Тест состоит из шагов. В конце каждого шага инфраструктура устройств («инфра») автоматически создает скриншот. Таким образом разработчики получают раскадровку из скриншотов, показывающих ход выполнения теста. Когда тестовый шаг заканчивается неудачей, это также четко обозначается (например «Не удалось нажать на кнопку Play»), поскольку у шага тестирования есть поля «Саммари» и «Описание ошибки».

Инфраструктура

Netflix Device Farm

Ящик для девайсов

Netflix, вероятно, была одной из первых компаний, у которой появилась специальная лаборатория для тестирования на устройствах; это было еще до появления сторонних сервисов типа Firebase Test Lab. Инфраструктура нашей лаборатории обладает многими функциями:

  • Таргетирована на определенные типы устройств
  • Запись видео во время теста
  • Запись скриншотов
  • Запись всех логов

Функции, которые есть только в Netflix:

  • Сотовая вышка, чтобы мы могли тестировать Wi-Fi и сотовые соединения; у Netflix есть своя физическая сотовая вышка в лаборатории, к которой подключаются устройства.
  • Тонкая настройка сети, чтобы можно было имитировать медленные сети
  • Автоматическое отключение системных обновлений на устройствах, чтобы блокировать их на определенном уровне ОС
  • Использование только raw-команд adb для установки и запуска тестов (эта инфраструктура существовала до появления таких фреймворков, как Gradle Managed Devices или Flank)
  • Запуск набора автотестов для A/B тестирования
  • Тестирование аппаратного и программного обеспечения для проверки того, что устройство не теряет кадры — для наших партнеров, чтобы убедиться, что их устройства поддерживают воспроизведение в Netflix должным образом; также есть программа квалификации для устройств, проверяющая поддержку HDR и других кодеков.

Если вам интересно узнать больше подробностей, загляните в соответствующий раздел блога Netflix.

Как справляемся с моргающими тестами

Как уже говорилось выше, нестабильность тестов — одна из самых сложных проблем, она связана с нестабильными по своей сути тестами на физических устройствах. Инструментарий должен быть настроен таким образом, чтобы:

  • Минимизировать нестабильность
  • Выявление причин нестабильности
  • Уведомление команд, которым принадлежат нестабильные тесты.

Инструментарий, который мы создали для управления моргающими тестами, обладает следующими возможностями:

  • Автоматически определяет пакет PR (Pull Request), в котором тест начал давать сбой, и уведомляет об этом авторов PR.
  • Тесты могут быть помечены как стабильные/нестабильные/отключенные вместо использования аннотаций @Ignore; это используется для временного отключения подмножества тестов при возникновении проблем с бекендом, чтобы в PR не сообщалось о ложных срабатываниях.
  • Автоматизация, позволяющая определить, можно ли перевести тест в статус Stable, используя свободные устройства для автоматической оценки стабильности теста.
  • Автоматизированные правила IfTTT (If This Then That) для повторного запуска тестов, игнорирования временных сбоев, или осмотра/ремонта устройства.
  • Отчеты о сбоях позволяют удобно фильтровать сбои в зависимости от производителя устройства, ОС или ящика, в котором находится устройство. Например, здесь показана частота сбоев теста в течение определенного периода времени для таких факторов окружения:
Netflix Testing Environments

Сбои с течением времени, сгруппированные по факторам окружения, таким как бэкэнд на staging/prod, версия ОС, телефон/планшет.

  • Отчет о сбоях позволяет посмотреть историю ошибок, чтобы определить наиболее распространенные причины сбоев, а также скриншоты:
Netflix Devices
  • Тесты можно вручную настроить на многократный запуск на разных устройствах, версиях ОС или типах устройств (телефон/планшет), чтобы воспроизвести нестабильные тесты.

Пайплайн

У нас типичный CI-конвейер PR (Pull Request), в котором выполняются юнит-тесты (включая тесты Paparazzi и Robolectric), lint, ktLint и Detekt. Запуск примерно 1000 тестов на устройствах является частью процесса PR. Также в процессе PR запускается подмножество дымовых тестов полностью обфусцированного приложения, готового к отправке в аппстор (предыдущие тесты на устройствах запускаются на частично обфусцированном приложении).

Дополнительные автотесты на устройствах выполняются как часть набора post-merge тестов. Когда мержатся серии PR, дополнительное покрытие обеспечивается автотестами, которые не могут быть запущены на PR, поскольку мы стараемся, чтобы набор автотестов PR на устройствах не превышал 30 минут.

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

В идеальном мире у вас бесконечные ресурсы для проведения всех тестов. Если бы у вас было бесконечное количество девайсов, вы могли бы запускать все тесты параллельно. Если бы у вас было бесконечное количество серверов, вы могли бы запускать все свои тесты параллельно. Если бы у вас было и то, и другое, вы могли бы запускать все тесты после каждого PR. А в реальном мире нужен сбалансированный подход, при котором выполняется достаточное количество тестов после PR, postmerge-тестов и т. д., чтобы предотвратить проблемы, чтобы клиенты были довольны, а команды работали продуктивно.

Покрытие

Тестовое покрытие устройств — это всегда компромиссы. На PR вы хотите максимизировать охват, но минимизировать время. Для post-merge/Daily/Weekly время менее важно.

При тестировании на устройствах у нас есть двумерная матрица: версия ОС, и типы устройств (телефон/планшет). Проблемы с макетами случаются довольно часто, поэтому мы всегда проводим тесты на телефоне+планшете. Мы автоматизируем тесты на «раскладушках», но там свои сложности, например возможность протестировать макеты до/после/во время складывания смартфона.

Для PR мы обычно используем так называемую «узкую сетку», что означает, что тест может работать на любой версии ОС. В Postmerge/Daily/Weekly мы используем так называемую «широкую сетку», что означает, что тест работает на всех версиях ОС. Компромисс заключается в том, что если произойдет сбой, специфичный для конкретной ОС, он может выглядеть как нестабильный тест на PR, и не будет обнаружен до тех пор, пока не произойдет реальный сбой.

Что в планах

Тестирование постоянно развивается по мере изучения того, что и как работает, или появления новых технологий и фреймворков. Сейчас мы изучаем возможность использования эмуляторов для ускорения PR-тестирования. Мы также оцениваем использование Roborazzi, чтобы сократить тестирование скриншотов устройств; Roborazzi позволяет тестировать взаимодействия, а Paparazzi — нет. Мы создаем модульную систему «демо-приложений», которая позволяет тестировать на уровне фич, а не на уровне приложений. «

Medium

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас