В предыдущей статье я поделился своим опытом автоматизации на Robot Framework. Теперь же речь пойдет о несколько другом подходе к тестированию API для проекта на Kotlin.
Воспользовавшись свободой выбора стека технологий и опираясь на желание попробовать «в бою» что-то новое, я обратился к Rest-Assured. Не без некоторых сложностей мы с коллегами запустили тесты, а по итогам освоения подхода записали его в список ключевых для подобного рода задач.
![image](https://habrastorage.org/r/w780q1/webt/dp/fb/py/dpfbpyb84ceqlsxp7b0ixsvo9wq.jpeg)
(изображение используется на правах пародии)
Началось все с того, что поступил запрос на автоматизацию тестирования API на одном из новых проектов в сегменте Ad-tech. Мы делали Campaign Manager, DMP и несколько интеграций со сторонними системами. А перед тестерами стояла предельно простая задача: автоматизировать смоук-тестирование для дальнейшей интеграции в CI и постоянного мониторинга состояния данного API, так как на него завязываются сторонние системы и корректность работы критична. Проверка бизнес-логики в данном случае не требовалась, поскольку тестируемый сервис по сути является прокси-интерфейсом.
С точки зрения автоматизации тестирования мы успели поработать в самых разных областях — 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. После изучения статьи написать рабочую версию тестов моего сервиса не составило труда. Они получились простые, легко читаемые и понятные.
Проект стартовал, и когда первые структуры ответов были описаны, началась подготовка тестовой документации и написание автотестов. Для понимания общей картины приведу полный стек автотестов:
Когда первые тесты были написаны, стала очевидна проблема правильной параметризации. Передавать простые значения данных и ожидаемого результата казалось крайне неэффективно и некрасиво. Решить эту проблему до отпуска я не успел, но с ней разобрались коллеги, которых привлекли на время моего отсутствия. Для красивой и легко читаемой параметризации они решили сделать базовый класс с функциями добавления, получения и удаления параметров объекта, от которого наследовались все классы, объекты которых использовались для вызова соответствующих методов API.
![image](https://habrastorage.org/r/w1560/webt/9j/vt/7r/9jvt7rmcd1w3wddoqmomp-_cpz4.png)
Это позволило буквально на лету создавать необходимые данные и передавать их в параметрах теста.
![image](https://habrastorage.org/r/w1560/webt/ll/rx/5o/llrx5opu0wbwa29_nmbywqxsubq.png)
Rest-Assured позволяет десериализовать ответ в требуемый класс. Полученный ответ раскладывается в заранее известную структуру с помощью Jackson. Классы для десериализации выглядят максимально просто — описываются все ожидаемые поля с их типами.
![image](https://habrastorage.org/r/w1560/webt/kw/kh/jr/kwkhjrtsj5zd6kxm0lkhcnaewqy.png)
Дальше продолжается простая работа с объектами и ассертации конкретных полей и их значений.
Самое сложное и неочевидное, с чем мне довелось столкнуться в данным подходе, это невозможность передать в Rest-Assured в качестве одного из параметров null (результат — падение в NullPointerException). Судя по всему, это известная проблема, но исправлять ситуацию разработчик не собирается, рекомендуя либо отправлять поле пустым, либо не отправлять его вовсе. С этой проблемой мы столкнулись уже на том этапе, когда основа проекта была готова и оставалось только дописать тестовые классы. Поэтому просто немного подкорректировали свой код.
В целом подход мне понравился. Любопытно, что через некоторое время он начал применяться и на проекте другого заказчика (правда, не с нашей подачи). А собранный стек для автоматизации тестирования API (JUnit + AssertJ + Rest-Assured) мы вынесли в категорию ключевых для проектов на Java/Kotlin. В Python его тянуть будет нелогично, поскольку лучше, чтобы компетенции разработки и тестирования пересекались.
Также стоит заметить, что одно из преимуществ этого решения заключается в его масштабируемости и адаптируемости под сложную логику. А значит, оно наилучшим образом подходит для серьезных проектов, где действительно есть смысл в описании больших объектов, чистом коде и блочной архитектуре, когда есть отдельные куски, отвечающие за подготовку, передачу и получение данных в читаемом виде, а также последующую проверку данных на соответствие каким-то условиям. В маленькие проекты все это тянуть бессмысленно.
Примерно за месяц нам удалось автоматизировать порядка 250 тестов и обеспечить требуемый уровень покрытия. Конечно же, после окончания процесса автоматизации для всех желающих была проведена внутренняя презентация, чтобы сотрудники имели представление о проделанной работе и могли перенять полученный на данном проекте опыт.
Автор статьи: Дмитрий Мастеров
P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на наши страницы в VK, FB или Telegram-канал, чтобы узнавать обо всех наших публикациях и других новостях компании Maxilect.
Воспользовавшись свободой выбора стека технологий и опираясь на желание попробовать «в бою» что-то новое, я обратился к Rest-Assured. Не без некоторых сложностей мы с коллегами запустили тесты, а по итогам освоения подхода записали его в список ключевых для подобного рода задач.
![image](https://habrastorage.org/webt/dp/fb/py/dpfbpyb84ceqlsxp7b0ixsvo9wq.jpeg)
(изображение используется на правах пародии)
Началось все с того, что поступил запрос на автоматизацию тестирования 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.
![image](https://habrastorage.org/webt/9j/vt/7r/9jvt7rmcd1w3wddoqmomp-_cpz4.png)
Это позволило буквально на лету создавать необходимые данные и передавать их в параметрах теста.
![image](https://habrastorage.org/webt/ll/rx/5o/llrx5opu0wbwa29_nmbywqxsubq.png)
Rest-Assured позволяет десериализовать ответ в требуемый класс. Полученный ответ раскладывается в заранее известную структуру с помощью Jackson. Классы для десериализации выглядят максимально просто — описываются все ожидаемые поля с их типами.
![image](https://habrastorage.org/webt/kw/kh/jr/kwkhjrtsj5zd6kxm0lkhcnaewqy.png)
Дальше продолжается простая работа с объектами и ассертации конкретных полей и их значений.
Самое сложное и неочевидное, с чем мне довелось столкнуться в данным подходе, это невозможность передать в Rest-Assured в качестве одного из параметров null (результат — падение в NullPointerException). Судя по всему, это известная проблема, но исправлять ситуацию разработчик не собирается, рекомендуя либо отправлять поле пустым, либо не отправлять его вовсе. С этой проблемой мы столкнулись уже на том этапе, когда основа проекта была готова и оставалось только дописать тестовые классы. Поэтому просто немного подкорректировали свой код.
В целом подход мне понравился. Любопытно, что через некоторое время он начал применяться и на проекте другого заказчика (правда, не с нашей подачи). А собранный стек для автоматизации тестирования API (JUnit + AssertJ + Rest-Assured) мы вынесли в категорию ключевых для проектов на Java/Kotlin. В Python его тянуть будет нелогично, поскольку лучше, чтобы компетенции разработки и тестирования пересекались.
Также стоит заметить, что одно из преимуществ этого решения заключается в его масштабируемости и адаптируемости под сложную логику. А значит, оно наилучшим образом подходит для серьезных проектов, где действительно есть смысл в описании больших объектов, чистом коде и блочной архитектуре, когда есть отдельные куски, отвечающие за подготовку, передачу и получение данных в читаемом виде, а также последующую проверку данных на соответствие каким-то условиям. В маленькие проекты все это тянуть бессмысленно.
Итоги
Примерно за месяц нам удалось автоматизировать порядка 250 тестов и обеспечить требуемый уровень покрытия. Конечно же, после окончания процесса автоматизации для всех желающих была проведена внутренняя презентация, чтобы сотрудники имели представление о проделанной работе и могли перенять полученный на данном проекте опыт.
Автор статьи: Дмитрий Мастеров
P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на наши страницы в VK, FB или Telegram-канал, чтобы узнавать обо всех наших публикациях и других новостях компании Maxilect.