Site icon AppTractor

Тестирование Android-приложений в масштабе 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 была расформирована несколько лет назад, и теперь автоматизированные тесты принадлежат каждой из функциональных подкоманд, но по-прежнему есть 2 вспомогательных SDET команды, которые помогают различным командам по мере необходимости. QA (Quality Assurance) вручную тестирует релизы перед их загрузкой в качестве финального «дымового  теста».

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

Текущий подход к тестированию

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

Пирамида тестирования, показывающая слои снизу вверх: модульные тесты, скриншот тесты, сквозные автоматизированные тесты, дымовые тесты

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

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

Фреймворки для модульных тестов

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

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

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

Нестабильность в модульных тестах

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

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

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

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

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

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

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

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

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

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

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

PageObjects и шаги тестирования

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

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

Инфраструктура автоматизации тестирования на устройствах

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

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

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

Работа с нестабильностью тестов

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

В инструментарий, который мы создали для управления нестабильностью, есть:

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

Конвейеры CI/тестирования

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

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

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

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

Покрытие

Охват устройств — это набор компромиссов. На PR вы хотите максимизировать охват, но минимизировать время. При тестировании после слияния, ежедневном и еженедельном, время менее важно.

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

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

Будущее тестирования

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

Источник

Exit mobile version