unit-testirovanie v flutter: ot osnovnykh elementov rabochego protsessa do slozhnykh stsenariev

By Anil G 11 Min Read

Интерес к Flutter достиг беспрецедентного уровня и это не только приятно, но и давно назрело. Flutter это открытый SDK от Google, который поддерживает Android, iOS, macOS, Web, Windows и Linux. Все эти платформы обслуживаются единым кодовым базом Flutter.

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

Что такое автоматизированное тестирование?

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

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

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

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

«Тестирование показывает наличие ошибок, а не их отсутствие». Эдсгер Дейкстра

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

  • анализируем код;
  • подготавливаем мок-данные;
  • определяем группу тестов;
  • задаём сигнатуры тест-функций для каждой группы;
  • пишем сами тесты.

Что такое модульное тестирование?

Модульное тестирование это этап автоматизированного тестирования, в котором проверяется надёжность небольших фрагментов кода путём их прогонки через разные варианты использования. В роли «юнита» может выступать всё что угодно: переменная, функция, метод, класс или состояние.

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

Классический модульный тест состоит из трёх этапов:

  • Arrange (подготовка)
  • Act (действие)
  • Assert (проверка)

Во время этапа Arrange создаётся объект или элемент кода, который нужно протестировать, а также подготавливаются все необходимые условия. Это может включать настройку переменных состояния, моков и других зависимостей. В некоторых случаях подготовка может быть минимальной или вовсе не потребоваться.

На этапе Act мы выполняем тестируемый юнит: передаём аргументы, изменяем состояние или вызываем нужный метод, затем фиксируем результат (если он есть).

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

Насколько важно модульное тестирование?

Писать и запускать модульные тесты довольно просто. Это экономит значительное количество времени.

Модульное тестирование позволяет находить ошибки на ранних этапах разработки. Это приводит к существенной экономии времени и средств.

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

Мы реже избегаем рефакторинга только из-за страха «сломать» код. Наличие модульных тестов даёт уверенность в том, что можно спокойно улучшать архитектуру тесты подскажут, если что-то пошло не так.

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

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

Что можно тестировать с помощью модульного тестирования?

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

Обычно модульное тестирование сосредоточено на следующих аспектах:

  • Переменные состояния
  • Вызовы функций и переменных
  • Аргументы функции
  • Возвращаемые значения

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

В рамках юнита мы проверяем следующие сценарии (и не только):

  • Корректность значения константы или final-переменной
  • Начальные значения переменных состояния
  • Вызывается ли определённая функция 1…n раз
  • Не вызывается ли функция вовсе
  • Обновляются ли переменные состояния ожидаемым образом
  • Соответствует ли результат работы юнита ожидаемому
  • Проверка на пустые значения для строк, списков или других сложных структур данных особенно важно, если мы их итерируем
  • Проверка на null (только для типов, допускающих null; в Dart большинство типов null-safe)
  • Проверка типа переменной или аргумента (не всегда нужно, если грамотно используем типизацию Dart)

Несколько важных особенностей работы Flutter

При создании проекта Flutter автоматически подключает тестовую библиотеку flutter_test, что упрощает процесс тестирования. Благодаря этому Flutter может читать, запускать и анализировать модульные тесты.

Папка test также создаётся автоматически. Очень важно не переименовывать и не перемещать её иначе Flutter не сможет корректно обнаруживать и запускать тесты. Кроме того, имена тестовых файлов должны обязательно заканчиваться на _test.dart, потому что Flutter использует этот суффикс для идентификации тестов.

Организация тестов

Мы создадим структурированную систему размещения тестовых файлов, где каждая группа тестов будет иметь своё понятное «место». Поскольку Flutter требует хранить тесты в папке test, имеет смысл повторить в ней структуру исходного кода.

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

Такая структура делает проект более прозрачным. Команде будет проще понимать, какие части кода имеют покрытие тестами, а какие нет.

Каковы лучшие практики модульного тестирования?

  • Модульные тесты должны работать быстро
  • Модульные тесты должны быть простыми
  • Модульные тесты должны быть детерминированными
  • В модульных тестах фокус должен быть на одном юните
  • В модульных тестах допустимо дублировать код
  • Описание теста должно быть максимально понятным

Модульные тесты должны быть быстрыми.
Во время разработки мы постоянно запускаем весь тестовый набор, поэтому каждый юнит-тест должен выполняться менее чем за минуту. Это позволяет быстрее находить и исправлять ошибки. Если тест работает дольше, его обычно переносят в CI-пайплайн.

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

Модульные тесты должны быть детерминированными.
Юнит-тест без каких-либо изменений в исходном коде должен всегда давать одинаковый результат независимо от времени, места, окружения. Результат теста не должен зависеть от внешних факторов: текущего времени, базы данных, интернета, нативных API. Обычно такие зависимости мы заменяем моками.

Фокус должен быть на одном юните.
Юнит-тест должен проверять только один конкретный модуль. Внутри теста мы не должны тестировать зависимости юнита только сам юнит.

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

Описание теста должно быть простым.
Хорошее описание теста включает четыре части:

  1. Какой юнит тестируется
  2. В каком состоянии он находится
  3. Какое действие мы выполняем
  4. Какой результат ожидаем

Мокирование

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

Предположим, юнит зависит от веб-API. На живом сервере тест может проходить успешно, но медленно. Если же сервер недоступен тест упадёт. В итоге получаем недетерминированный тест, поведение которого мы не контролируем. Если у веб-сервера произошёл сбой это не наша вина, но тест всё равно ломается.
Именно здесь и применяется мокирование.

В модульном тестировании мокирование означает подмену внешних зависимостей тестовыми «фейковыми» объектами. Это позволяет полностью изолировать код, который мы проверяем, от поведения внешних систем.

MockTail

MockTail означает «мокирование без генерации кода».

Основная цель MockTail предоставить простой, понятный и привычный API для создания моков в Dart, с полной поддержкой null safety, без ручного написания мок-классов и без генерации кода.

Эту библиотеку создал Феликс Ангелов автор популярных пакетов bloc, equatable и других. MockTail выделяется тем, что имеет стопроцентное покрытие тестами и очень удобен в использовании.

FAQs

Что такое модульное тестирование (Unit Testing) во Flutter?

Модульные тесты используются для проверки работы отдельного метода, функции или класса. Пакет test предоставляет базовую инфраструктуру для написания модульных тестов, а пакет flutter_test дополняет её утилитами для тестирования виджетов.

Сколько видов тестирования поддерживает Flutter?

Flutter поддерживает три вида тестирования:

  1. Unit tests проверяют работу отдельных функций, методов или классов.

  2. Widget tests позволяют тестировать функциональность и рендеринг виджетов без запуска всего приложения.

  3. Integration tests комплексные (end-to-end) тесты, которые проверяют работу всего приложения целиком.

Как создать модульный тест во Flutter?

Процесс модульного тестирования во Flutter аналогичен большинству других фреймворков.
Он включает следующие этапы:

  • определить классы и функции, которые нужно протестировать;
  • проанализировать код;
  • настроить мокирование данных;
  • сформировать группы тестов;
  • описать сигнатуры тестовых функций;
  • написать тесты и выполнить их.

Почему модульное тестирование важно?

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

Share This Article
Leave a comment