Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead агентства продуктовой разработки Amiga. Мы с командой подготовили для вас перевод статьи о тестировании во Flutter. Рассмотрим Unit-тестирование, Widget-тестирование, Golden-тесты и интеграционное тестирование. Всем приятного чтения!

В этой статье приведено много примеров и часто встречаемых ошибок, с которыми сталкиваются специалисты, когда пишут тесты. Также показано, как писать код, чтобы сделать тестирование проще, и как использовать AI-инструменты, такие как ChatGPT или GitHub Copilot для увеличения скорости написания тестов.

И перед тем, как приступить к самой статье, хочу вас познакомить с нашим телеграмм-каналом Flutter.Много. Мы ведем его всей командой мобильных разработчиком Amiga и рассказываем о личном опыте, делимся полезными плагинами\библиотеками, переводами статей и кейсами. Присоединяйтесь!

Почему нужно писать тесты?

Есть 3 основных причины, почему написание тестов необходимо:

  1. Обеспечение качества

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

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

  2. Помощь в рефакторинге

    Рефакторинг кода максимально рискован, поскольку легко непреднамеренно внести баги. Однако, при запуске тестов после рефакторинга, баги обнаружатся, если они есть.

  3. Экономия времени и денег

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

Методы тестирования

Существует 4 основных метода тестирования на Flutter: модульные или Unit-тесты, Widget-тесты, Golden тесты и интеграционные тесты. Они различаются по назначению, масштабу и времени выполнения.

Методы тестирования
Методы тестирования

Unit-тесты

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

Цель Unit-тестов — проверить корректность работы функции или метода с разными вводными условиями.

Например, представим, что у нас есть 3 функции: saveToken, getToken, и login:

bool saveToken( String token) { 
  return sharedPreferences.saveToken(token); 
} 

String  get token => secureStorage.token; // как получить код 

bool login( String email, String password) { 
  final token = apiClient.login(email, password); 
  return saveToken(token); 
}

С модульным тестированием нужно написать тесты для каждой функции по отдельности.

Например, для функции saveToken, когда приходит token, ответ функции должен быть true или false в зависимости от тестового сценария. Когда вызываем геттер token, функция должна вернуть токен, хранящийся в SecureStorage. Для функции login, нужно вернуть true или false в зависимости от переданных на вход email и password. 

Помимо тестирования ответа, зависящего от входных данных, также можно протестировать, была ли вызвана функция apiClient.login или сколько раз она была вызвана. Если ее не вызывали или делали это больше одного раза, то в коде скорее всего есть баг.

Написание Unit-тестов — лишь необходимое условие для того, чтобы удостовериться, что приложение работает корректно.

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

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

Аналогично, геттер token используется только для получения токена из SecureStorage и ему все равно, где он был сохранен. Когда каждая функция выполняется корректно, проходят и Unit-тесты. Но при запуске приложения эти 2 функции будут вызывать ошибку, тогда на помощь приходят интеграционные тесты.

Интеграционные тесты

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

Также существуют end-to-end (или e2e) тесты, которые похожи на интеграционные, но проверяют не только внутреннюю логику приложения, но и все взаимодействия с различными API.

Источник: Reddit
Источник: Reddit

Например, нам нужно протестировать функционал входа в аккаунт. Когда пользователь вводит корректные email и пароль, мы попадаем на Главный экран (Home screen).

Источник: Youtube
Источник: Youtube

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

Unit-тесты и интеграционные тесты в основном используются для проверки логики приложения. Если нужно протестировать пользовательский интерфейс, например, «цвет кнопки соответствует дизайн», «кнопка включена или выключена» или «кнопку видно», то необходимо применить Widget-тесты и Golden тесты.

Widget-тесты

Цель Widget-тестов — проверить соответствие пользовательского интерфейса дизайну и корректную работу взаимодействия с ним.

Также как Unit-тесты, Widget-тестам не нужно запущенное приложение на реальном устройстве или эмуляторе.

Стоить отметить, что не все виджеты у нас получится протестировать при помощи Widget-тестов.

Golden тесты

Golden тесты — по сути те же Widget-тесты, но еще они позволяют проверить корректное расположение виджета на экране.

Например, кроме соответствия цвета кнопки дизайну, можно проверить включена, выключена или видно ли её пользователю.

Golden-тесты также проверяют расположение кнопки на экране в соответствии с дизайном. Это делается при помощи генерации изображения пользовательского интерфейса виджета, известных как Golden Images, и сравнения их с текущими. Если обе картинки совпадают, то тест пройдет. Сгенерировать golden images можно на нескольких устройствах с разным размером, таких как телефоны и планшеты.

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

Ожидаемый UI (golden тесты)
Ожидаемый UI (golden тесты)

Если поменять цвет Floating Action Button на красный и передвинуть вверх 2 виджета Text:

Текущий UI
Текущий UI

то Golden тесты найдут эти различия и уведомят об ошибках при сравнении изображений, как показано ниже:

isolatedDiff
isolatedDiff
maskedDiff
maskedDiff

Golden тесты экономят много времени и денег, так как проверяют правильность пользовательского интерфейса при помощи сравнения изображений, чего не могут обычные Widget-тесты.

Заключение

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

Больше интересного и полезного про Flutter — в телеграмм-канале Flutter. Много

Кейсы, личный опыт, обзор плагинов и библиотек. Присоединяйтесь!

Комментарии (1)


  1. babylon5215
    21.07.2024 07:41

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