Разделы портала

Онлайн-тренинги

.
Самые сложные автотесты
05.07.2023 00:00

Автор: Александр Романов (Oleksandr Romanov)
Оригинал статьи
Перевод: Ольга Алифанова

Действительно ли легко писать и поддерживать UI-тесты? Действительно ли юнит- и интеграционные тесты – самые сложные?

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

С чего все началось?


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

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

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

Юнит-тесты

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


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

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

Сложность внедрения юнит-тестов так же невелика, как издержки на поддержку. Вопрос тут только в том, каков уровень тестируемости кода фичи.

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

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

Интеграционное тестирование – одна из самых противоречивых тем для инженеров. У всех свое понимание того, что такое уровень интеграции.


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

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

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

Сложность внедрения у таких тестов – средняя. Основная проблема тут в правильной конфигурации зависимостей для сервиса.

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

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

End-to-end тесты

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

Как только вы освоите API Selenium Webdriver и научитесь использовать паттерн Page Object (и прочие), внедрение end-to-end тестов становится очень простым. Конечно, тут есть исключения: очень сложная заковыристая логика, экзотические браузеры, нестандартные API. Создание новых UI-тестов для разработчика довольно тривиальная задача по сравнению с юнит- и интеграционными тестами. Поэтому все убеждены, что end-to-end тесты – это самое простое.


Но тонкость тут в том, что автоматизированное тестирование – это не только внедрение новых тестов, это также их поддержка. Это дебаг и исправления. Тут и находится самое крупное препятствие, практически айсберг, для понимания end-to-end тестирования.

МНОЖЕСТВО внешних и внутренних факторов влияет на производительность и стабильность таких тестов:

  • Базы данных могут медленно выполнять репликацию и возвращать противоречивые данные.
  • Сотни, тысячи микросервисов бэкэнда могут в любой момент перестать работать, вызывая каскадные проблемы.
  • Критические сервисы могут получать данные от брокера сообщений (к примеру, Kafka) с задержкой.
  • Внешние службы также могут быть нестабильными или полностью недоступными.
  • Система может находиться под воздействием внешней DDoS-атаки.
  • Балансировщики нагрузки и кэши тоже могут нестабильно работать.
  • Web и мобильные приложения могут по-разному работать (в зависимости от модели телефона или версии браузера).
  • Запросы от браузера в тестах могут обрабатываться медленнее по сравнению с реальным пользователем.
  • Тест-фреймворк может работать с ошибками (особенно если множество тестов запускается параллельно).
  • Связи с контейнерами в Selenoid могут вообще испариться по какой-то причине.
  • Соединение с фермой устройств также может быть точкой нестабильности.

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

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

Иногда исправление будет на уровне UI-теста или фреймворка. Иногда это изменение в требованиях или срочная необходимость исправления тестовых данных.

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

Как инженер-автоматизатор, вы не контролируете систему и ее зависимости. У вас есть ваш браузер – и на этом все!

Итак, какие же автотесты наиболее сложны?

Аспект

Юнит-тесты

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

End-to-end тесты

Усилия по внедрению

Низкие

Средние

Низкие

Усилия по поддержке

Низкие

Низкие

Высокие

Контроль зависимостей

Высокий

Средний

Низкий

Скорость обратной связи

Высокая

Средняя

Низкая

Нестабильность

Низкая

Средняя

высокая

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

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

End-to-end тесты могут принести пользу или быть наиболее сложными в поддержке автотестами. Это мощный источник фрустрации для инженеров-автоматизаторов.

Ожидаете ли вы стабильных UI-тестов от вашей команды автоматизаторов?

Обсудить в форуме