Практика тестирования бэкенда на Java + Rest-Assured

В предыдущей статье я поделился своим опытом автоматизации на Robot Framework. Теперь же речь пойдет о несколько другом подходе к тестированию API для проекта на Kotlin.

Воспользовавшись свободой выбора стека технологий и опираясь на желание попробовать «в бою» что-то новое, я обратился к Rest-Assured. Не без некоторых сложностей мы с коллегами запустили тесты, а по итогам освоения подхода записали его в список ключевых для подобного рода задач.

(изображение используется на правах пародии)

Началось все с того, что поступил запрос на автоматизацию тестирования API на одном из новых проектов в сегменте Ad-tech. Мы делали Campaign Manager, DMP и несколько интеграций со сторонними системами. А перед тестерами стояла предельно простая задача: автоматизировать смоук-тестирование для дальнейшей интеграции в CI и постоянного мониторинга состояния данного API, так как на него завязываются сторонние системы и корректность работы критична. Проверка бизнес-логики в данном случае не требовалась, поскольку тестируемый сервис по сути является прокси-интерфейсом.

Почему Rest-Assured?

С точки зрения автоматизации тестирования мы успели поработать в самых разных областях — UI, web, mobile, desktop, бэкенд, REST API, SOAP. Прошлый проект дал нам довольно большой опыт на Robot Framework, поэтому логичнее всего было бы использовать именно его. Большинство в команде тестирования с ним знакомы, и если потребуется срочная замена специалиста, сделано это будет быстро и фактически безболезненно. Но было принято иное решение.

Во-первых, сам проект был написан на Kotlin и собирался с помощью Gradle. При этом на стадии проектирования автотесты в отдельный проект решили не выделять. Как было отмечено в комментариях к предыдущей статье, склеить Java (Kotlin) и Robot Framework — большая боль, поэтому не было никакого смысла обращаться к RF. Плюс, оглядываясь на работу с роботом, мне было интересно попробовать иной подход. Тем более что на данном проекте мы могли самостоятельно выбирать стек технологий — бизнес не ставил своих условий.

В поисках идей я обратился к нашей команде разработки, а также коллегам из тестирования, и CTO посоветовал присмотреться к Rest-Assured (rest-assured.io). После того, как я почитал документацию и примеры тестов в открытых источниках, подход, предлагаемый Rest-Assured, показался мне очень привлекательным. Он подразумевает прием ответа от бэкенда в виде JSON, который мы десериализуем в старые добрые объекты Java.

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

Для точности отмечу, что приблизительно тот же failproof-тест можно было бы получить на RF, но немного другим путем. Однако рассказ сегодня не об этом. Лично мне был удобнее и понятнее заход со стороны Rest-Assured с сущностью, у которой есть определенные поля и методы.

Проба пера

Прежде чем начать использовать стек на реальном проекте, я решил опробовать его на небольшом CRUD-сервисе. Чтобы сразу не практиковать собственные ошибки, решил поискать best practises по данному стеку и достаточно быстро нашел статью Филиппа Хауера, где он отразил свой опыт автоматизации на Rest-Assured. После изучения статьи написать рабочую версию тестов моего сервиса не составило труда. Они получились простые, легко читаемые и понятные.

В бой!

Проект стартовал, и когда первые структуры ответов были описаны, началась подготовка тестовой документации и написание автотестов. Для понимания общей картины приведу полный стек автотестов:

  • Java,
  • JUnit4 — исполнение и параметризация тестовых сценариев (стандартная аннотация @Parametrize),
  • Rest-Assured — построение и выполнение запросов,
  • AssertJ — валидация полученных значений,
  • Allure — построение отчетности,
  • Gradle — сборка,
  • Jackson — десериализация.

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

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

Rest-Assured позволяет десериализовать ответ в требуемый класс. Полученный ответ раскладывается в заранее известную структуру с помощью Jackson. Классы для десериализации выглядят максимально просто — описываются все ожидаемые поля с их типами.

Дальше продолжается простая работа с объектами и ассертации конкретных полей и их значений.
Самое сложное и неочевидное, с чем мне довелось столкнуться в данным подходе, это невозможность передать в Rest-Assured в качестве одного из параметров null (результат — падение в NullPointerException). Судя по всему, это известная проблема, но исправлять ситуацию разработчик не собирается, рекомендуя либо отправлять поле пустым, либо не отправлять его вовсе. С этой проблемой мы столкнулись уже на том этапе, когда основа проекта была готова и оставалось только дописать тестовые классы. Поэтому просто немного подкорректировали свой код.

В целом подход мне понравился. Любопытно, что через некоторое время он начал применяться и на проекте другого заказчика (правда, не с нашей подачи). А собранный стек для автоматизации тестирования API (JUnit + AssertJ + Rest-Assured) мы вынесли в категорию ключевых для проектов на Java/Kotlin. В Python его тянуть будет нелогично, поскольку лучше, чтобы компетенции разработки и тестирования пересекались.

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

Итоги

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

Все статьи