Тестирование интеграции с API
Задача интеграции с API встречается очень часто, по популярности она уступает только обмену между базами и выгрузке из Excel.
Есть много разных вариантов API:
-
Есть мастодонты – Google, Яндекс – у них много разных сервисов, с которыми можно интегрироваться. Например, получение переводов, генерация и чтение текстов, прогнозы погоды.
-
Часто приходится интегрироваться с «Почтой России» или OZON.RU – если ваша компания занимается перевозками или отправкой товаров.
-
С Telegram – если надо отправлять или получать данные и писать ботов.
-
И, конечно, наши государственные сервисы: например, ФНС, если вам нужно получать данные о контрагентах.
У всех этих вариантов есть нечто общее:
-
Клиент-серверное взаимодействие, когда вы от себя отправляете данные, а все обрабатывается на стороне сервиса.
-
Сервер не будет хранить ваше состояние – ему без разницы, какой вы запрос отправите, состояние запроса вы должны соблюдать самостоятельно.
-
Используется немного кэширования.
-
Формат ответа – обычно HTML, XML, JSON либо бинарные файлы (если вы что-то получаете в виде картинок или pdf-документов).
Проблемы тестирования
Какие проблемы возникают в тестировании с API?
-
Основное – стабильность тестов, потому что когда у вас внешнее API – вы целиком и полностью зависите от того, что происходит на сервере. Сервис может уйти на обслуживание, не выдержать нагрузки и, соответственно, ваши тесты тоже перестают проходить.
-
Возможность покрытия сценариев. Тяжело реализовать все сценарии, которые вы можете придумать, когда у вас внешнее API.
-
Большая проблема – скорость ответов. Зависит от того, какая нагрузка на сервере, которым вы пользуетесь, сколько до вас идет ответ.
-
Следующая проблема – риск работы с реальными данными. Если вы тестируете наживую, то можете пересечься с вашими реальными данными, которыми оперирует учетная система. Ничем хорошим это не закончится.
-
Еще есть проблема: можно увлечься, и вместо того, чтобы тестировать, как ваше приложение взаимодействует с API, начать тестировать API. Вы думаете: «А что будет, если я отправлю такой запрос, а если такой, а если здесь пробел поставлю?» И получается, что вместо того, чтобы придумать сценарий, как вы будете взаимодействовать с сервером, вы смотрите, как ответит сервер.
Методы решения проблем
Какие есть методы решения проблем?
Самый простой вариант – вам предоставят тестовый стенд. Вам выделяют отдельную область, в которой вы будете тестировать свои алгоритмы.
В чем тут может быть проблема? Если стенд один, а разработчиков несколько – ваши тесты могут мешать друг другу.
Следующий вариант – написать свой сервер.
Все программисты, если что-то не работает на стороне, начинают писать свое. Делаем небольшой сервер – берем Python или OneScript.Web – и пишем небольшие заглушки. Мы знаем, какие запросы отправляем, готовим под них ответы.
В чем здесь проблема? Его надо писать. Это будет ваше отдельное приложение, которое надо поддерживать.
Следующий вариант – использовать специальные mock-сервера (заглушки).
Для них все равно нужно будет готовить данные.
При этом вам нужно не забывать за этими данными следить – вы поставили заглушку, у вас тестирование прекрасно проходит, но если на «боевой» уже поменялся API, и у вас тестируется не реальная ситуация.
Mock-сервер можно использовать только для тестов. Никаких альфа-бета-тестирований на mock-сервере проводить не стоит – для этого нужно использовать только боевой контур.
Последний вариант – решить все архитектурно, написать приложение так, чтобы оно само в себе содержало тестовые данные и могло их в любой момент использовать. Это самый сложный вариант, мы не будем на нем останавливаться
Мы разберем мокирование.
Мокирование
Мокирование от английского mock – «заглушка». Выглядит это следующим образом.
Запускается тест.
-
В первую очередь, тест готовит себе данные – обращается к специально запущенному приложению, передает ему информацию о том, что собирается отправить в него такие-то данные и ожидает получить на них такие-то ответы.
-
Далее тест обращается к вашей системе, чтобы она отправила запросы не на реальный сервер, а на заглушку.
-
Заглушка присылает ответы, а тест верифицирует: правильные ли данные получила система.
Вариантов таких мок-серверов много, перечислю самые популярные.
-
Один из первых, который мне попадался – это Postman, в нем есть возможность мокирования. Postman – одно из мастхэв-решений, если вы пишете интеграцию. Работать с запросами в нем очень удобно. И также в нем есть собственная возможность мокирования. Достаточно слабенькая, но есть.
-
SoapUI – достаточно мощное решение, там есть запись ваших запросов, их воспроизведение, упаковка в сценарии. Достаточно популярное решение для мокирования.
-
Еще есть сервис WireMock.
-
Есть сервис МоckServer. С ним я еще не разобрался, он достаточно сложный.
-
И есть JSON-Server – легкое решение, в нем есть база данных, основанная на JSON. Он умеет сохранять данные и преобразовывать.
Но я вам хочу рассказать про работу с WireMock.
WireMock – запуск, возможности, управление
Что такое WireMock?
-
WireMock – приложение на Java, что уже хорошо. Значит, вы сможете его запускать и на Windows, и на Linux.
-
Он умеет работать в режиме JUnit, когда вы пишете тесты на Java, и Standalone – как отдельно запускаемое приложение. С 1С я его чаще использую как Standalone.
-
Он очень хорошо управляется через HTTP-запросы. Когда вы его как Standalone подняли, он управляется, отправляя запросы на нужный адрес.
-
Все настройки он хранит в JSON-формате – это очень удобно, потому что JSON хорошочитаемый, его удобно смотреть при code-review.
-
У него есть функция записи и воспроизведения – чуть позже расскажу, что это такое.
-
Также он умеет назначать приоритеты шаблонам, умеет работать с состояниями и симулировать ошибки.
Чтобы было понятнее – давайте разберем конкретную задачу.
Практика
Наша задача – сделать погодный виджет для Бухгалтерии 3.0. Я достаточно часто встречал такие обработки на Инфостарте – видимо, многие клиенты хотят знать погоду, не выходя из 1С.
Мы выведем на главной странице Бухгалтерии 3.0 текущий прогноз погоды и постараемся максимально закрыть это тестами.
Реализация выглядит вот так – это расширение, которое я нашел на Инфостарте в публикации //infostart.ru/public/801039/. Оно выводит погоду – сейчас +18 градусов.
Давайте накроем его код тестом.
Тест делаем как обычно, кнопконажималкой – запускаем 1С, получаем нужный элемент и проверяем его данные.
Насколько это будет стабильный тест? С утра у нас было +7, днем стало +15, два часа назад пошел дождь, но теперь светит солнце. Соответственно, тест будет совершенно нестабилен – каждый раз, когда меняется погода, тест будет падать.
Как раз его мы и попробуем замокировать.
Подготовка
Первым делом, нужно провести подготовку.
-
Настроить запуск 1С в режиме тестирования.
-
Научиться перенаправлять запросы – чтобы запросы отправлялись не к внешнему сервису, а к нашему.
-
Подготовить набор ответов.
Начнем с запуска в режиме тестирования. Я обычно реализую такой метод через параметр сеанса, который зависит от параметра запуска.
Добавляем параметр запуска --usemock, и в случае, если система запущена с этим параметром, говорим, что вместо запроса конкретному серверу будем переходить на наш, подготовленный.
Теперь насчет подготовки мокированных ответов.
У WireMock есть специальноеприложение – WireMockUI, которое позволяет настроить следующую вещь: мы в нем указываем, какой сервис API мы хотим мокировать, на какой сервер мы его перенаправляем.
Дальше мы его запускаем в режиме записи. Можем сделать все, что нам нужно в 1С или приложении. WireMock перехватывает эти запросы и готовит необходимые файлики ответов и шаблоны для запросов. Мы это забираем и запускаем с уже заготовленными шаблонами.
Вот так это выглядит. Мы включили запись, что-то потыкали, получили нужные ответы, забираем эти результаты себе. Складываем это в какую-то папку с проектом или еще куда. И после этого запускаем WireMock с указанием того, что все наши шаблоны лежат в нужной папке.
Шаблон выглядит вот так: здесь URL запроса и ответ.
Содержание большого JSON-ответа можно упаковать в отдельный файлик – его адрес будет указан в свойства bodyFileName.
Вот так выглядит этот JSON-файл ответа.
Теперь мы можем переписать наш тест следующим образом – указываем, где у нас живет WireMock со всеми настройками, запускаем его. По итогу тест получается стабильным. Сколько бы раз мы его ни запускали, у нас получится одна и та же температура на главной странице.
Макеты
Давайте подробнее разберемся, что такое макет.
Макет – это шаблон, по которому будет определяться, откуда брать ответ. Он должен описать вашу строку запуска, включая переданные в нее параметры и заголовки.
Самое первое, что есть в шаблонах – какой HTTP-метод мы используем.
Например, можно указать, что этот шаблон будет использоваться для обработки всех запросов на такой-то адрес, которые используют метод GET.
Дальше мы разбираем, куда уходит наш запрос.
-
Первый вариант – использовать свойство url, которое описывает полное соответствие, включая все параметры. Мы прямо говорим, что если мы делаем GET-запрос по такому-то URL – возвращай ответ. Это не очень удобно, потому что в коде могут поменяться местами параметры, некоторые параметры будут являться обязательными или необязательными.
-
Поэтому следующий вариант – в свойстве urlPattern написать регулярное выражение, которое будет разбирать вашу строку.
-
И совсем простой вариант – с помощью свойства urlPath указывать только начальный путь. Чаще всего я использую именно этот вариант, когда указываю, что запрос будет по такому-то URL, а потом отдельно обрабатываю параметры запроса через свойство queryParameters.
Есть несколько вариантов обработки параметров:
-
equalTo – это точное соответствие, когда вы говорите, чему должен быть равен этот параметр;
-
matches – регулярное выражение, которому этот параметр должен соответствовать.
Если параметр необязательный, его все равно нужно указать. Ведь если запрос придет с этим параметром, то шаблон в этом случае уже не сработает.
С помощью свойства bodyPatterns есть возможность обрабатывать тело запроса. В зависимости от того, какой запрос вы обрабатываете, можно использовать:
-
XPath-язык – свойство matchesXPath;
-
JSONPath – свойство matchesJsonPath;
-
или описывать полное совпадение тела запроса через свойства equalToXml и equalToJson.
Это все, что касается шаблона запроса.
Дальше этот шаблон переадресовывает к шаблонам ответа. То есть, для каждого шаблона на запрос мы готовим ответ.
Вы можете готовить динамические ответы:
-
указывать дату в определенном формате;
-
разбирать входящие параметры, если это требуется;
-
там есть конкатенация строк и т.д.
Но динамические шаблоны ответа – это не очень хорошая практика, потому что, по-хорошему, вы должны готовить шаблоны под каждый тест.
Stateful
Разберемся, что делать, если у нас есть зависимость от состояния. Сервер не хранит состояния обмена по API, но бэкэнд может какие-то состояния хранить.
Например, если у вас сервис учитывает контроль остатков. Вы делаете запрос остатков, списываете товар, делаете еще один запрос – у вас товар должен закончиться. Получается, у вас есть несколько состояний.
WireMock позволяет это реализовать:
-
у него есть понятие сценария;
-
каждый сценарий должен находиться в определенной стадии;
-
эти стадии можно переключать как прямым запросом по HTTP, так и самим сценарием.
Например, здесь на слайде указан сценарий «To do list», и текущее состояние у него стартовое (“requiredScenarioState”: “Started”).
Давайте реализуем такой контроль состояний у нас – чтобы для разных состояний за окном погода на виджете менялась.
Нам потребуется два шаблона:
-
один стартовый (“requiredScenarioState”: “Started”),
-
другой я назвал bad (“requiredScenarioState”: “bad”) – сейчас объясню, почему.
Логика такая – когда наш сервер стартует, он находится в стартовом состоянии и подает нужный ответ.
А когда мы переключим его в новое состояние, данные на виджете должны измениться.
Как это будет выглядеть в тесте? Мы указываем, что работаем со сценарием Weather.
Прямо в коде явно переключаем его состояние – тогда через 30 секунд у нас меняется погода на виджете.
Аварийные ситуации – низкая скорость, обрыв, мусор
Плохо, когда пользователю показывают сообщения об ошибке. Они чаще всего пугаются, их закрывают и не сознаются, что у них были ошибки вообще.
Лучше постараться обработать эти аварийные ситуации – повторно отправить запрос или подсказать пользователю.
Но когда вы работаете с реальным API, с тем же тестовым стендом, протестировать низкую скорость или обрыв соединения – большая проблема. Проблема даже вручную это протестировать: наш тестировщик это проделывал, выключая вай-фай во время теста.
Как это можно реализовать с помощью мокирования?
Первое – задержка ответа. Прямо в шаблоне можно указать, что нам потребуется какая-то задержка.
-
Она бывает фиксированная, когда вы говорите, что каждый запрос должен быть задержан на 1000 миллисекунд, на 3 минуты...
-
Есть вариант показать, что это уже случайная величина на каждый запрос.
-
И есть интересный вариант – цепочка ответов, когда ваш запрос идет с задержкой в 1000 миллисекунд пятью пачками (каждые 200 миллисекунд отправляет пачку ответов) и в конце концов собирается. На слайде показано, как это может быть реализовано.
Еще интересный вариант, что делать, если вам пришел вообще невалидный ответ сервера:
-
вам может прийти пустой запрос;
-
может прийти мусор в ответе – когда вначале был json, а потом абракадабра;
-
и соединение может быть закрыто во время получения данных.
На слайде показано, как выглядит запрос Postman, когда приходит совсем пустой ответ – он не может ничего прочитать.
Если вы перехватите такую ошибку, 1С это более-менее переваривает.
Интересный вариант реализации – как протестировать обрыв связи. Реализуется как раз цепочками состояний.
-
есть стартовое состояние – Started;
-
дальше мы получаем валидное состояние с погодой – Good;
-
переключаем наш сценарий запросом CONNECTION_RESET_BY_PEER на состояние Conn_reset («Оборвать соединение»);
-
и снова переключаем на хорошее – Good.
Таким образом проверяем, что в случае обрыва соединения будет попытка его восстановить, и пользователю будет выведено сообщение.
Резюме
Как итог всего этого – вы получаете:
-
стабильную проверку позитивных и негативных сценариев, не угрожая продуктовому серверу;
-
вполне параллельно тестируете локально – тестировщики тестируют у себя, одновременно гоняются тесты на сервере, все замечательно;
-
также можете проверять неадекватные ситуации – сбои, менять ответы, если что-то пошло не так, мусор в ответы наваливать.
Все это позволит вам повышать качество ваших продуктов.
Полезные ссылки
Накину чуть-чуть полезных ссылок, что можно почитать подробнее:
-
Статья https://code.tutsplus.com/ru/tutorials/fake-rest-api-up-and-running-using-json-server--cms-27871, которая описывает работу с маленьким интересным сервисом JSON-Server.
-
Статья на Инфостарте //infostart.ru/public/1014870/ про использование SoapUI в качестве заглушек.
-
Документация на WireMock http://wiremock.org/docs/
Проекты на GitHub
-
Виджет «Погодка», который я использовал https://github.com/petypen/Pogogka20
-
Мой демо-стенд: https://github.com/KrapivinAndrey/infostart2020-DevOps1c-Mockdemo – можете зайти и посмотреть все сценарии, которые я вам показал. Плюс еще парочка реализованных. Возьмите и попробуйте их локально позапускать, посмотреть, как это все устроено.
Вопросы
Где взять шаги для фич, чтобы работать с WireMock?
Шаги можно взять там же, у меня в репозитории
Пробовали ли вы использовать OneScript.Web в качестве мок-сервера?
Как я уже сказал, когда пишешь сам, проблема в том, что нужно писать. Первый сценарий мы реализовали на Python, потом переписали на OneScript.Web. Но когда начинается усложнение системы, ты понимаешь, что у тебя есть твой код, теперь тебе еще нужно поддерживать свой мок-сервер. Зачем, если уже люди постарались и написали готовое решение.
Как изменяется состояние сервера? Это какая-то опция в командной строке его запуска?
Во-первых, нужно создать шаблон ответа для этой стадии при этом имени сценария. Само состояние меняется сценарием – там есть шаг, который напрямую направляет запрос к WireMock и меняет стадию. И при выполнении запроса есть опция, что этот запрос переключает на следующую стадию.
Непонятно, в каком формате смотреть результаты тестов.
Результаты тестов смотреть в привычном формате – Allure, Junit. Как обычно, мы проверяем результаты нашего тестирования в фреймворке тестирования – например, Vanessa ADD. Мы не тестируем API, мы тестируем поведение нашей системы – мы отправляем запрос, ожидаем, что поле будет иметь такое значение. Если это не так, очевидно, что тест провален.
*************
Данная статья написана по итогам доклада (видео), прочитанного на онлайн-митапе "DevOps в 1С: Тестирование и контроль качества решений на 1С".