Автоматизация тестирования, которая не ломается при первом редизайне

Как мы проектировали, внедряли и поддерживаем живую систему автотестов

52b4a2e1464da4124f347a49d8803e8f.png

Привет! Меня зовут Артем, я эксперт по тестированию в компании TData —разработчике высоконагруженных корпоративных решений для работы с данными в реальном времени. Мы создаём ПО, где особенно важны стабильность, отказоустойчивость и предсказуемость поведения — вне зависимости от нагрузки и сложности сценариев.

Автоматизация тестирования для нас — не просто способ снять нагрузку с ручных тестировщиков, а часть инженерной культуры. В этой статье поделюсь тем, как мы выстраивали автоматизацию: с чего начали, почему стартовали с UI, какие инструменты прижились, как справлялись с нестабильными тестами. Давайте разберёмся, как не заблудиться на этом увлекательном, но местами запутанном пути.

Зачем мы пишем автотесты и что они нам дают?

На старте всё кажется очевидным: «автотесты — это быстрее и лучше, чем руками». Но что это означает на практике?  

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

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

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

Всё это — не абстрактные плюсы, а вполне измеримые эффекты, которые мы в TData наблюдаем каждый день.

Почему начали с UI, а не с API 

Мы приняли решение начать автоматизацию тестирования с UI-части. Но почему? По классической пирамиде тестирования автоматизация должна начинаться с юнит- и API-тестов. Но мы выбрали другой путь — начали с UI. Причины были вполне прагматичными. Во-первых, хотелось сразу снизить нагрузку на тестировщиков, которые вручную прогоняли одни и те же сценарии из билда в билд. Во-вторых, было важно показать менеджерам результат, который можно увидеть глазами: как скрипт сам открывает интерфейс, кликает кнопки и проходит сценарий. Это производило гораздо большее впечатление, чем любые диаграммы с ожидаемой экономией ресурсов или предполагаемом росте продуктивности.

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

Наш стек технологий 

Для автоматизации UI тестов мы используем Selenide. В отличие от своего предшественника Selenium, Selenide проще поддерживать и развивать, особенно для тех, кто только встраивается в процесс. И, что самое главное, он быстрее. Короткий и выразительный синтаксис, встроенные ожидания, лаконичные конструкции — всё это помогло нам ускорить старт. 

В качестве тестового движка мы используем JUnit 5 — современный, гибкий и хорошо совместимый с остальным стеком. А чтобы тесты не бегали на локальной машине, а масштабировались по браузерам и версиям, мы настроили запуск через Selenoid. Контейнеры, удалённое выполнение, видеофиксация прогонов — всё это дало возможность полноценно интегрировать тестирование в CI. Если вам кажется, что это звучит сложно, не волнуйтесь! Я тоже так думал в начале своего пути.

Когда перешли к API, логичным выбором стала Rest Assured. По-моему, это лучшая библиотека на Java для автоматизации API. У нее много преимуществ перед конкурентами, начиная с интуитивно понятного синтаксиса и заканчивая высокой производительностью. API-методы удобно описываются, легко поддаются сериализации через POJO, и в целом эта часть автоматизации получилась более стабильной, чем UI.

Как мы пишем UI-автотесты — и почему без Page Object быстро всё развалится

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

Реальность такая: интерфейс меняется. Где-то добавили новый блок, где-то поменяли ID элемента, где-то кнопка переехала вниз. Если у вас сотни автотестов, и каждый напрямую обращается к UI-элементам, вы потратите дни, чтобы это всё поправить вручную.

Поэтому мы сразу внедрили паттерн проектирования Page Object. Суть простая: выносите описание элементов и взаимодействие с ними в отдельные классы. Один класс — один экран или компонент. Это помогает централизовать логику взаимодействия с UI: при изменении интерфейса нужно обновить только один файл, а не всю пачку тестов.

Пример. У нас есть интерфейс с несколькими виджетами: погода, такси, новости. Для каждого виджета нужно создать свой Page-класс в IntelliJ IDEA, чтобы было проще писать автотесты. Короче говоря, паттерн нужен для того, чтобы визуально вам было проще писать и поддерживать автотесты.

Итак, мы создали, например, Page, который назвали PageTaxi. В этом классе мы будем описывать элементы и методы к ним — и всё, больше ничего. Вся остальная логика будет перемещена в тестовый класс. Нам его также нужно создать и назовем его TestTaxi. В этом классе будут писаться сами тесты, которые в дальнейшем будут запускаться.

Ниже приведен пример одного из автотестов:

fb751a64fa966eb196974336681affe6.png

А вот пример Page-класса: сначала мы описываем элемент, с которым будем работать, а потом метод для этого элемента. Вот как это выглядит на практике:

8c3d2975a2038640352967999a1aa936.png

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

Если вы планируете развивать проект, а не переписывать его каждый месяц — без Page Object дальше будет больно. 

Как мы боремся с нестабильными UI-тестами (и что такое flaky)

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

Дальше интереснее — как мы в TData поддерживаем автотесты в живом состоянии, особенно когда они внезапно начинают сыпаться. Автотесты могут начать резко падать — иногда по вашей вине, иногда по вине разработчиков, а иногда по независимым от всех обстоятельствам. В целом, UI автотесты считаются самыми нестабильными из всех. Если вам знакомо слово flaky, то вы знаете, о чём речь: тест, который один раз падает, а потом вдруг проходит без изменений. Такие тесты опасны — они подрывают доверие ко всей автоматизации. Поэтому в нашей практике любой flaky — это сигнал, что нужно пересмотреть его логику и устойчивость.

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

5e30ed51e6210965e96b3b10e84fc060.png

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

Как мы работаем с данными в автотестах

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

Откуда берутся эти данные и как мы их готовим?

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

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

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

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

Интеграция Selenoid в CI: как мы запускаем UI-тесты в TData

Для автоматизации UI-тестирования мы используем Selenoid — инструмент для параллельного выполнения тестов на различных браузерах и платформах. Это позволяет нам эффективно проверять функциональность и производительность приложения, обеспечивая быструю обратную связь для разработчиков. Selenoid интегрирован в наш CI/CD-процесс через Jenkins: при каждом коммите запускается джоб, который параллельно прогоняет набор UI-тестов. Это позволяет покрывать кросс-браузерные сценарии и заметно сокращает общее время выполнения. Плюс — можно гибко масштабироваться при росте нагрузки.

Каждый тест запускается в изолированном контейнере с нужным браузером. Поддерживается VNC-доступ, запись видео и сбор логов — всё это критично, когда нужно разобраться, почему тест упал. После завершения теста контейнер удаляется, освобождая ресурсы. В результате мы получили стабильную и масштабируемую инфраструктуру UI-тестирования, удобную в сопровождении и прозрачную при анализе ошибок. Ниже — как выглядит один из автотестов в интерфейсе Selenoid:

e284767728aa3b03457a73eca6efcc69.png

Как писать API автотесты

После успешного освоения UI автотестов, пришло время перейти к автоматизации API тестов. Важно понимать, что API (Application Programming Interface) — это интерфейс, который позволяет различным приложениям взаимодействовать друг с другом. В отличие от фронтенда, здесь нет визуального слоя, и всё взаимодействие с системой происходит через запросы и ответы. По сути, API — это публичный контракт между сервисами. Его можно сравнить с меню в ресторане: вы видите, какие данные и функции доступны, и как их можно запросить.

Шаг 1: Изучение документации API 

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

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

Шаг 2: Тесты на Rest Assured

В качестве основного инструмента для написания API тестов мы используем библиотеку Rest Assured. Она предоставляет декларативный и лаконичный DSL для отправки HTTP-запросов и проверки ответов. Тест строится по цепочке: формируем запрос с нужными хедерами и параметрами, отправляем его, проверяем статус, валидируем структуру ответа и извлекаем нужные данные. Всё это читается как скрипт:  

1ecd630ceae9428b6287ae3518bc9f01.png

Шаг 3: Использование POJO-классов

В отличие от UI-автотестов, где применяется паттерн Page Object, API автотесты часто пишутся в одном классе. Однако для улучшения читаемости и поддержки кода рекомендуется использовать POJO (Plain Old Java Object) классы. Они помогают структурировать данные и делают код более понятным.

Вот пример POJO класса для пользователя:

62de35adfcb8d94133fd5fd4bee0d768.png

При помощи чего мы делаем отчеты автотестов в TData

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

Ниже — пример отчёта Allure после прогона 600 тестов с распределением по функциональным блокам:

9f5b063b90cacde50995f89f69306ed7.png

Кастомизация Allure отчетов с помощью аннотаций

Если вы обратили внимание на скриншоты выше, могли заметить, что над тестами в коде используются аннотации, например @DisplayName. Почти все аннотации у нас в проекте используются для кастомизации Allure отчета, и это тоже не исключение. Конкретно @DisplayName позволяет задать понятное и читаемое название для теста, которое будет отображаться в отчёте вместо имени метода. Вместо shouldReturn200WhenUserExists () вы видите, например: «Получение пользователя по ID — 200 OK при валидном токене». Такая детализация значительно повышает читаемость отчёта и помогает быстрее понять, что именно проверялось в каждом тесте. Мы стараемся описывать тесты так, чтобы даже человек, не писавший этот код, мог по отчёту понять суть сценария и причину возможного падения. Это особенно полезно при анализе регрессий или при разборе отчётов командой.

Собрали свою систему — делимся выводами 

Автоматизация тестирования — это не про «нажал кнопку — всё проверилось». Это про инфраструктуру, поддерживаемый код, подход к данным, стабильность и здравый смысл. Мы в TData прошли путь от первых UI-скриптов до комплексной системы с API-тестами, CI-интеграцией, репортами, логированием и защитой от flaky.

Если вы только начинаете — не бойтесь стартовать с простого. Не обязательно сразу строить идеальную пирамиду тестов, выравнивать всё по best practices и настраивать пять окружений. Важно, чтобы автоматизация решала конкретные задачи и давала ценность здесь и сейчас: экономила время, снимала рутину, помогала находить баги раньше.

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

© Habrahabr.ru