Тестирование интеграции с 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С".
Вступайте в нашу телеграмм-группу Инфостарт

