Многие используют SoapUI для того, чтобы тестировать как сам API, так и приложения, обращающиеся к API. Довольно гибкий инструмент, позволяющий, например, экспортировать swagger файл API и сгенерировать Mock-service на его основе.
Не так давно у нас в компании я столкнулся с похожей задачей, но с нетривиальными условиями. Исходные данные: необходимо протестировать серверное приложение, которое получает на вход задачу, в процессе выполнения обращается к АПИ, каждый последующий запрос зависит от ответа АПИ. Логика вшита в приложение. То есть своеобразный черный ящик, где нужно протестировать множество выходов из сценария, когда вход в сценарий один.
Ниже я предлагаю пример решения, которое позволило достаточно просто встроить его в регрессионную инфраструктуру, а также имеющее запас для масштабирования, в случае увеличения покрытия диапазона сценариев и их сложности.
Для начала создаем Mock-service в SoapUI. Это делается в несколько кликов:
Теперь перейдем к созданию заглушек для запросов. Так как в поставленной задаче вход в сценарий один, у нас есть варианты:
- использовать идентификатор сценария и передавать его в каждом запросе, а в каждой заглушке определять ответ в зависимости от этого идентификатора;
- заранее указать список ответов для каждой заглушки и хранить их в глобальных переменных перед прогоном теста.
Первый вариант может использоваться в случае, когда необходимо получать ответ от заглушки одновременно для нескольких запросов. Реализация требует от клиентского приложения способности передавать определенный идентификатор в каждом запросе к АПИ. В нашем случае это было практически невозможно, а одновременное тестирования нескольких сценариев не требовалось, поэтому был выбран второй вариант.
Итак, для назначения списка ответов заглушек выполняем следующий запрос перед запуском теста:
Post: http://mockserver:8080/setscenario
Body: ScenarioId=0&Authentication=200_OK&AutoSystemHome=400_TokenIsMissing…
В Mock-сервисе добавляем обработку запроса «SetScenario». Он имеет всего два ответа: «200_OK» в случае корректной обработки входящего запроса, и «400_BadRequest» если запрос был составлен неверно:
В скрипте мы распределяем в глобальные переменные значения ответов для каждой заглушки:
def reqBody = mockRequest.getRequestContent() //считываем тело запроса
def reqBodyParams = [:]
reqBody.tokenize("&").each //собираем входные параметры, переданные в запросе
{
param->
def keyAndValue = param.split("=")
reqBodyParams[keyAndValue[0]]=keyAndValue[1]
}
if (reqBodyParams.containsKey('ScenarioId')) // ID сценария как обязательный параметр (необходим для упрощения разбора логов); сюда можно добавить проверки на прочие обязательные для вас параметры запроса
{
// назначаем глобальные переменные, в которых будем хранить переданные значения ответов для заглушек, “?:” – означает какое дефолтное значение будет назначено в случае отсутствия переданного:
context.mockService.setPropertyValue("ScenarioId", reqBodyParams["ScenarioId"] ?: "0")
context.mockService.setPropertyValue("Authentication", reqBodyParams["Authentication"] ?: "200_OK")
context.mockService.setPropertyValue("AutoSystemHome", reqBodyParams["AutoSystemHome"] ?: "200_OK")
// и так далее для каждого метода …
return "200_OK"
}
else
{
return "400_BadRequest"
}
Назначенные переменные можно увидеть в окне настроек сервиса:
Таким образом, мы описываем всю логику работы Mock-сервиса именно в этом методе, а в заглушках для методов АПИ достаточно написать скрипт, считывающий значение ответа из глобальной переменной:
Authentication = context.mockService.getPropertyValue("Authentication")
return "${Authentication}"
AutoSystemHome = context.mockService.getPropertyValue("AutoSystemHome")
return "${AutoSystemHome}"
Если необходимо добавить сценарии таймаутов, задержки в ответах, то добавляем переменную delay, к примеру:
Post: http://mockserver:8080/setscenario
Body: ScenarioId=0&Delay=600&Authentication=200_OK &AutoSystemHome=400_TokenIsMissing…
А в скрипт заглушки добавляем:
…
Authentication = context.mockService.getPropertyValue("Authentication")
Delay = context.mockService.getPropertyValue("Delay").toInteger()
sleep(Delay)
return "${Authentication}"
Если необходимо поддержать повторный запрос, то передаем список ответов:
Body: Authentication:400_MissingParametersClientId;400_MissingParametersClientId;200_OK
А в скрипте обработки добавляем tokenize и удаление уже отправленного ответа, в случае если он не последний:
def Authentication = []
Authentication = context.mockService.getPropertyValue("Authentication").tokenize("%3B")
if (Authentication.size() > 1)
{
Authentication.remove(0)
Authentication = Authentication.join("%3B")
context.mockService.setPropertyValue("Authentication", Authentication)
}
Как итог мы получили простой Mock-сервис, который легко перемещать между тестовыми стендами и средами, ведь файл проекта – это xml-файл. Сервис запускается сразу после импортирования, без дополнительных настроек (за исключением изменения адреса сервера и порта, конечно). В данный момент он помогает нам тестировать приложение независимо от стабильности АПИ сервера и возможных временных периодов его недоступности.
Что планируем добавить: интегрировать это решение для тестирования приложений до и во время разработки самого АПИ. Например, когда уже готово его описание в виде swagger-файла, но сервер в процессе настройки. Циклы разработки АПИ и клиентских приложений не всегда совпадают. В этот момент полезно на чем-то тестировать разрабатываемое клиентское приложение, и Mock-сервис может сильно помочь.
UPD: в случае если появятся вопросы и полезные замечания.
Автор: Alexey