Привет! Меня зовут Мария Лещинская, я QA-специалист в Surf. Наша компания разрабатывает нативные приложения с 2011 года, а с 2018-го мы занимаемся ещё и разработкой под Flutter.

В этом материале сравним возможности тестирования нативных и кроссплатформенных приложений. Я поделюсь впечатлениями от работы с Flutter и расскажу, какие инструменты мы в Surf используем при тестировании, чем Flutter удобен и с какими проблемами мы столкнулись.



Возможности тестирования во Flutter не уступают нативным


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

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

Нам было важно, чтобы привычные возможности оставались и при тестировании Flutter-приложений.

Автотестирование

При автотестировании нативных приложений Surf работает с фреймворком Calabash и Ruby. Когда появился Flutter, нам первым делом стало интересно: можно ли не использовать Calabash, но при этом в полной мере работать с автотестами так, как мы привыкли, — или даже круче. 

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

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

Flutter также поддерживает Behavior Driven Development — BDD — подход. Мы используем его для UI-тестов. В качестве языка выбрали Gherkin: в нём можно использовать feature-файлы, писать сценарии на английском и русском языках. Он понятный, в нём есть реализация шагов сценариев без дополнительных аргументов внутри или с ними, возможность кастомизации запуска автотестов: например, прогон некоторых сценариев по тегам, а не всех написанных тестов целиком. 

Чтобы использовать Gherkin при тестировании Flutter-приложений, подключили opensource-фреймворк flutter_gherkin

Когда мы поняли, что автотесты на Flutter есть, нам стало интересно, какие отличия между технологиями Calabash и Dart+Gherkin, какой подход лучше. Давайте вместе сравним их.

1. Feature-файлы абсолютно идентичны при обоих подходах.

Например, сценарий авторизации по пин-коду будет корректно восприниматься и на Dart, и на Ruby с использованием Calabash:

Сценарий: Корректная авторизация в приложении с первого раза (короткий код)
    Когда Я запускаю приложение
    И Я вижу экран ввода короткого кода
    Тогда Я ввожу корректный короткий код
    И Я вижу главный экран

Обе технологии поддерживают русский, английский и другие языки.

2. Steps отличаются в реализации.
Dart + flutter_gherkin
Calabash
class TapAnErrorButtonOnPinCodeScreen extends ThenWithWorld<FlutterWorld> {
  @override
  Future<void> executeStep() async {
    final elemFromReportAnErrorScreen = find.byValueKey('reportAnErrorButton');
    await FlutterDriverUtils.tap(world.driver, elemFromReportAnErrorScreen);
  }
  @override
  RegExp get pattern => RegExp(r"Я нажимаю Сообщить об ошибке на Экране пин-кода");
}

When(/^Я нажимаю Сообщить об ошибке на Экране пин-кода$/) do
wait_element("* id:'reportAnErrorButton'")
tap_on("* id:'reportAnErrorButton'")
end

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

3. Для конфигурации тестов и работы с ними Flutter использует дополнительный файл .dart. При работе с Calabash такого единственного файла не существует.

Сказать, что это недостаток внутри Flutter или Calabash, на наш взгляд, нельзя — это всего лишь специфика работы с конкретными инструментами и технологиями.

4. Для удобства работы с элементами в приложении необходимо, чтобы у каждого элемента был свой id. При работе с автотестами с Calabash нужно заранее позаботиться о том, чтобы на обеих платформах в тестируемых приложениях были id. На Dart можно добавлять id в процессе написания автотестов, не перезаливая отдельно файлы iOS и Android приложений — это удобно и экономит время. 

Наш вывод: автотесты на Dart совсем не уступают автотестированию с помощью фреймворка Calabash.
 
Прокси

Для повышения покрытия приложения кейсами в Surf используют программы для чтения и подмены трафика — например, Charles. Анализ клиент-серверного взаимодействия позволяет:

  1. Определять, есть ли реальное взаимодействие с бэкэндом.
  2. Находить, на чьей стороне проблема: на клиенте или на сервере.
  3. Упрощать и ускорять проверки на готовых тестовых данных, не привлекая разработчиков сервера.
  4. Анализировать поведение мобильного приложения в различных сетевых условиях: отвала запросов, задержки, больших данных. Charles позволяет определить неверно сформированные запросы от клиента к серверу и избежать зацикленности при обращении к серверу, и, как следствие, нагревания устройства и быстрого разряда.

У Dart свой клиент для работы с сетью. Так как все запросы идут через него, то необходимые настройки для работы с прокси нужно вводить внутри приложения. Для удобства тестировщиков все нужные настройки вынесены на отдельный экран: мы в Surf используем Debug Screen, который разработали сами.

Debug Screen — это дополнительный экран настроек, который доступен только из debug-сборки и помогает в тестировании. В нём можно выбрать необходимый сервер, включить использование чтения и сохранения http-запросов в приложении, просмотреть fcm-токен устройства и не только — возможностей для тестирования предусмотрено много. 

Debug Screen кастомный: разработчики добавляют на него дополнительные элементы по просьбе тестировщиков — например, поля для настройки прокси из приложения. Поэтому возможности работы с Charles у нас полные: можно подключить прокси-сервер на Debug Screen без перезапуска приложения.

Как видим, возможности при тестировании Flutter-приложений не ограничены. Всё, с чем мы привыкли работать при нативе, есть и удобно в использовании. Это не может не радовать.

Проблемы: баги фреймворка, недоработки в сторонних библиотеках, ожидаемое нативное поведение


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

Расскажем, на что обращать внимание, тестируя Flutter-приложения. Предупреждён — значит вооружён.

Баги Flutter-фреймворка

При тестировании столкнулись с проблемой отображения и форматирования шрифтов на iOS: межбуквенный интервал на iOS-платформе был заметно шире, чем на Android. Это вызывало большое количество визуальных багов. 

Выяснилось, что проблема в самом фреймворке. Когда наши разработчики мобильных приложений обратились к ребятам из сообщества разработчиков Flutter-фреймворка с просьбой пофиксить такой неприятный баг, вскоре вышло обновление фреймворка и отображение текста на iOS было исправлено.

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

Недоработки в сторонних библиотеках

На iOS версий 10 и 11 встречались недоработки реализации в библиотеках сторонних разработчиков. Например, мы исправляли баг, когда пермишен на доступ к уведомлениям всплывал сразу при запуске приложения, а не по кнопке, как планировалось по ТЗ и дизайну.

Такие проблемы могут встретиться и в кроссплатформе, и в нативе. Решаются фиксами внутри проекта, либо совместно с разработчиками библиотек.

Работа с ожидаемым нативным поведением

При длительном использовании и тестировании нативных приложений на iOS и Android легко спрогнозировать ожидание пользователя от различного поведения приложений. Так, например, возврат назад бэксвайпом на iOS — стандартный жест. А в Android он не нужен.

Системные диалоги на обеих платформах отличаются: в iOS необходимо запросить пермишен на доступ к уведомлениям, а на Android такой доступ дан по умолчанию. 

Именно такие части специфики ОС зачастую приходится допиливать вручную. А иногда — выпиливать, если вдруг ожидаемое поведение для iOS платформы перекочевало в Android, как было с бэксвайпом.

В нативных приложениях проблемы вроде обновления экрана, некорректного сворачивания приложения, работы приложения, несвойственной для текущей ОС, встречаются редко: инструменты и технологии для разработки приложения на определённую платформу заведомо направлены на охват всех версий и возможностей конкретной системы. 

При тестировании одного из Flutter-приложений cтолкнулись с интересной ситуацией: возможность для обновления экрана была недоступна на iOS устройствах с чёлкой — начиная с iPhoneX и выше. При этом iOS-устройства без чёлки и Android функционировали корректно. 

Еще один баг встретился нам на Android версии 6: при сворачивании приложение полностью выгружалось из памяти.

Такие баги были пофикшены нашими разработчиками внутри проекта.

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

Хочется чтобы кроссплатформа содержала все тонкости реализации от нативной разработки. Конечно, недочёты при работе с Flutter ощущаются, но это не проблема: просто к кроссплатформе нужен свой подход.

Преимущества: единая кодовая база, одна команда разработки


Единая кодовая база сокращает время тестирования

Причиной багов может стать нечёткое ТЗ, отсутствие отрисованных состояний в дизайне, обратная совместимость при обновлении бэка. Заводить такие баги проще, потому что нужно создавать в два раза меньше задач, и это уже экономит время. 

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

Логика новых фич на обеих платформах тоже одинакова, так как написан один код: тестирование сложных процессов внутри приложения сводится к тестированию их на одной платформе и подтверждению на другой. Мы проводим полный цикл активностей на одной платформе: делаем исследовательское тестирование, прогоны по фичам, смоук/санити/фулл, анализируем обратную связь. После этого остаётся только подтвердить качество исследовательским тестированием на другой платформе. Такой подход экономит время тестирования логики примерно в 1,3 раза.
Пример

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

Исправив баги и убедившись на различных сценариях, что сбор аналитики в приложении работает корректно на одной из платформ (например, iOS), с большей уверенностью можем считать, что не встретим баг на другой платформе (Android), связанный с треком лишнего события или отсутствия нужного event в системе аналитики.

Если сборки на обе платформы нужно отдать заказчику в один день, выходят они одновременно и QA-инженер на проекте один, времени на проверку при нативной разработке может не хватить: цикл тестирования нужно выполнить на обеих платформах отдельно. Тестируя кроссплатформенные приложения, экономим время: регрессионное тестирование обеих платформ проходит в рамках единственного цикла. 

Мы попробовали примерно оценить тестирование двух похожих проектов — один на нативных Android и iOS, второй на Flutter — и сравнили их пофично. 

Анализ и тестирование проводились на одном устройстве iOS и на одном устройстве Android-платформы. Как видно на практике, Flutter действительно даёт выигрыш во времени, хоть и не в два раза. Это объяснимо: полностью убрать тестирование на одной из двух платформ нельзя. Как ни крути, у них разная специфика и ориентированность на пользователя.
 
Native iOS
Native Android
Flutter Android+iOS
Восстановление пароля
2h
2h
3h 20m
Авторизация
1h 30m
1h 30m
2h 20m
Пуш-уведомления
2h
2h
4h

При тестировании готовой фичи, не влияющей стопроцентно на специфику операционной системы и не написанной кастомно под каждую платформу отдельно, время проверки Flutter-приложения на обеих платформах сокращается примерно в 1,3—1,5 раза. Например, фичи авторизации и восстановления пароля, не имеющие специфичного поведения на разных платформах, сокращают время тестирования Flutter-версии в 1,3 раза.

Что касается фич, которые требуют кастомного поведения от каждой платформы, ждать сокращения времени не стоит. Поведение для iOS и Android ожидается разное, а значит, и тестировать обе платформы нужно полностью и отдельно друг от друга. Например, тестировать пуш-уведомления зачастую необходимо полным циклом на обеих платформах из-за различий в пермишенах, работы с подключением уведомлений, настройки пушера для отправки уведомлений на iOS и Android, а также других тонкостей реализации — чтобы поддерживалась привычная для пользователя работа с уведомлениями, соблюдались ТЗ и дизайн.

Организовать коммуникацию внутри команды проще

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

Во-первых, проверять приложение легче за реализацией одной команды, чем работать с двумя разными реализациями на двух платформах. Специалист по обеспечению качества, конечно, заинтересован в том, чтобы обладать полной информацией о статусе приложения, как на платформе iOS, так и на платформе Android. При работе с Flutter это проще.

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

Нам нравится тестировать Flutter-приложения 


Быть частью крутого сообщества

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

При этом разработчики из коммьюнити Flutter-фреймворка быстро дают обратную связь по выявленным проблемам, улучшают библиотеку и позволяют нам контрибьютить в проект: мы идём вперёд вместе, и это приятно. 

Быть специалистом

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

При разработке и тестировании нативных приложений невозможно собрать iOS приложение из, например, Android Studio или Visual Studio Code. При работе с Flutter IDE едина и для Android, и для iOS. Это круто.

Быть самостоятельным

При работе с Flutter мы в Surf сталкиваемся с очень разными проектами: от e-commerce до банкинга. Практика показала, что QA-инженер может в одиночку справляться с тестированием обеих платформ. Подключать ещё одного специалиста необходимо разве что ближе к релизу, когда темп работы увеличивается, а времени на выполнение задач остаётся всё меньше. 

Flutter — шаг вперёд


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

Опыт разработки и тестирования Flutter-приложений показал Surf, что этот фреймворк — большой шаг вперёд.