Многие используют SoapUI для того, чтобы тестировать как сам API, так и приложения, обращающиеся к API. Довольно гибкий инструмент, позволяющий, например, экспортировать swagger файл API и сгенерировать Mock-service на его основе.

Не так давно у нас в компании я столкнулся с похожей задачей, но с нетривиальными условиями. Исходные данные: необходимо протестировать серверное приложение, которое получает на вход задачу, в процессе выполнения обращается к АПИ, каждый последующий запрос зависит от ответа АПИ. Логика вшита в приложение. То есть своеобразный черный ящик, где нужно протестировать множество выходов из сценария, когда вход в сценарий один.

image

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

Для начала создаем Mock-service в SoapUI. Это делается в несколько кликов:

image

Теперь перейдем к созданию заглушек для запросов. Так как в поставленной задаче вход в сценарий один, у нас есть варианты:

  1. использовать идентификатор сценария и передавать его в каждом запросе, а в каждой заглушке определять ответ в зависимости от этого идентификатора;
  2. заранее указать список ответов для каждой заглушки и хранить их в глобальных переменных перед прогоном теста.

Первый вариант может использоваться в случае, когда необходимо получать ответ от заглушки одновременно для нескольких запросов. Реализация требует от клиентского приложения способности передавать определенный идентификатор в каждом запросе к АПИ. В нашем случае это было практически невозможно, а одновременное тестирования нескольких сценариев не требовалось, поэтому был выбран второй вариант.

Итак, для назначения списка ответов заглушек выполняем следующий запрос перед запуском теста:

Post: http://mockserver:8080/setscenario
Body: ScenarioId=0&Authentication=200_OK&AutoSystemHome=400_TokenIsMissing…

В Mock-сервисе добавляем обработку запроса «SetScenario». Он имеет всего два ответа: «200_OK» в случае корректной обработки входящего запроса, и «400_BadRequest» если запрос был составлен неверно:

image

В скрипте мы распределяем в глобальные переменные значения ответов для каждой заглушки:

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"
}

Назначенные переменные можно увидеть в окне настроек сервиса:

image

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

image

Authentication = context.mockService.getPropertyValue("Authentication")
return "${Authentication}"

image

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: в случае если появятся вопросы и полезные замечания.

Комментарии (2)


  1. qwez
    12.11.2018 08:59

    Неплохо, но все равно выглядит как-то костыльнно :)
    Я про логику динамического формирования заглушки: метод `setscenario` записывает переменные, а остальные их считывают. А можно в скрипте SoapUi создавать новую заглушку (заглушка создает заглушку, хехе)? Так было бы более канонично, плюс убивать ее сразу после теста.
    Ну и параллелить точно уже не получится при таком подходе (ну либо очень осторожно).

    А почему не рассматриваете развитие в сторону какого-нибудь мок-сервера (тот же WireMock)? SoapUi все-таки больше инструмент для ручных тестов, в нем нет той гибкости, какую нам могут предложить различные специализированные библиотеки.


  1. alexeyohilkov Автор
    12.11.2018 12:41

    Насчет выбора метода согласен, что это не самое красивое решение, но в поставленных условиях думаю, что оно оптимально. Создать заглушку — не обязательно(еще более «костыльно» :D ), можно вообще шагнуть дальше, а обращаться к библиотеке ответов и в одной заглушке возвращать нужный :)
    При выборе SoapUI — наличие GUI позволяет быстро настроить сервис. Для небольших и несложных проектов здорово подходит, поэтому выбор пал на него. Если рассматривать более сложные проекты, то наверное действительно стоит смотреть в сторону WireMock или MockServer.