Всем привет!
В первой части статьи я рассказала про SoapUI в целом, а также работу с ним через командную строку, Docker и Python. В этой части мы подробно рассмотрим структуру XML‑файла SoapUI‑проекта: основные теги и нюансы работы с ними.
Навигация:
Будет полезно всем, кто хочет работать с файлом проекта не только через графический интерфейс, но и при автоматической генерации файлов или копировании.
Напомню, что статья написана по мотивам автоматизации запуска юнит‑тестов (UT, unit‑тесты). Для ее выполнения мне нужно было отрефакторить более 70 проектов. Вручную делать этого не хотелось, поэтому я стала оптимизировать отдельные этапы.
Общая структура проекта
Проект в SoapUI — это файл с расширением XML. Он состоит из корневого тега soapui‑project и дочерних ему сущностей, которые описывают тесты и их окружение.
Если вы собрались вручную что‑то исправлять в исходнике проекта, то предварительно закройте его в SoapUI, либо закройте само приложение. В первом случае нужно будет перезагрузить проект (Reload Project или F5). Во втором — приложение все сделает за вас.
Будьте аккуратнее с форматированием исходника в IDE, так как теги с большим количеством текста, например, groovy, могут быть перенесены некорректно (появится лишний перевод строки).
Упрощенно структура будет выглядеть следующим образом:
На скриншоте удалены все атрибуты и содержание тегов. Основной неймспейс — это «http://eviware.com/soapui/config», соответствующий ему префикс «con» есть у всех тегов в проекте. Для удобства в рамках статьи он опущен, т. е. в тексте указано «testStep», но в файле будет «<con:testStep>». Все значения атрибутов тегов в XML нужно указывать в двойных кавычках.
У многих тегов встречается атрибут id, при генерации XML его заполнять не обязательно — при первом открытии проекта через GUI пропущенные идентификаторы проставятся автоматически.
Атрибуты тега soapui-project
Атрибут | Описание |
name | Название проекта, в GUI отображается в списке открытых проектов |
activeEnvironment | |
resourceRoot | |
soapui‑version | Версия SoapUI, например, 5.7.0 |
abortOnError | Флаг «прерывать при получении ошибки или нет» |
runType | Тип запуска: последовательный (SEQUENTIAL) или параллельный (PARALLELL) |
На первом уровне дочерних тегов находятся:
description — описание проекта
settings — настройки (встречается во многих сущностях)
interface — может быть несколько, wsdl‑ или wadl‑интерфейс
mockService — может быть несколько, теги для REST‑ и SOAP‑mock отличаются
properties — это CustomProperties у проекта, testSuite, testCase, testStep, mockService, mockResponse и т. д. Состоит из набора тегов property, которые, в свою очередь, состоят из name и value.
На уровне soapui-project тег settings появляется, когда настройки проекта не совпадают с настройками SoapUI. Например, если там не настроено кеширование WSDL-интерфейсов, а в проекте cashed = True.
В XML-файле:
<con:settings>
<con:setting id="WsdlSettings@cache-wsdls">true</con:setting>
</con:settings>
Если глобальные или проектные настройки изменятся, то запись в XML-файле удалена не будет. Таким образом можно обеспечить одинаковую работу проекта на разных окружениях (например, у разных разработчиков).
Custom Properties
Custom properties (также пользовательские свойства, характеристики) — удобный инструмент для настройки. Этот тег встречается на разных уровнях проекта вплоть до Test Step и Mock Response. Доступ к ним осуществляется следующим образом:
${#<место хранения>#<название переменной>}
<vacationType>${#Project#VacationType}</vacationType>
<currentDate>${#TestCase#CurrentDateTime}</currentDate>
В наших проектах с unit‑тестами встречался хардкод значений пользовательских свойств. В большинстве случаев это уместно, но есть и исключения. Самое простое — даты. Если тестируемое приложение должно как‑то особенно их обрабатывать, например, проверять, входит ли она в разрешенный интервал, то в UT лучше использовать скрипты: они позволят решить проблему динамической генерации данных без участия человека.
Рассмотрим простой вариант установки даты начала и окончания. Напишем скрипт на Groovy:
import java.text.SimpleDateFormat;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -14);
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.ms+03:00").format(cal.getTime());
Создадим Custom Property и вставим скрипт в графу value:
В XML‑файле код будет выглядеть следующим образом:
Поскольку файл не предназначен для редактирования человеком, то весь скрипт превращается в очень длинную строку. Что‑то простое можно описать таким образом, но для более сложных случаев лучше создать отдельный Test Step.
Возьмем похожий скрипт для получения даты и создадим Test Step с названием current_date. Его результат сохранится в локальное пользовательское свойство result.
Далее с помощью Test Step вида TransferProperty установим результат выполнения скрипта в curr_date:
В графе Source указывается название объекта‑источника переменной (в нашем случае, это название Test Step), в Property — название Custom Property (в примере это будет result). В Target: целевой объект и целевое пользовательское свойство. На скриншоте выше маппинг производится в TestSuite «GetBird» в его свойство curr_date.
Transfer Property проставляет результат выполнения скрипта в поле value у соответствующей Custom Property. Значение записывается в XML‑файл и хранится до тех пор, пока его не изменят вручную или запустив тесты. В рамках автозапуска SoapUI‑проекта этот подход гарантирует актуальность данных, а в GUI можно работать и не используя скрипты.
Custom Properties можно импортировать и экспортировать, а также задавать их значение через консоль. SoapUI выгружает свойства в файл в формате: PROPERTY_NAME=PROPERTY_VALUE.
При запуске через консоль применяются только те параметры, которые уже были в проекте, новые придется создавать отдельно. В GUI же подобных проблем нет, так как есть настройка «Create Missing»:
Interface
Одна из возможных причин большого объема исходного файла кроется в теге interface. Раньше я пробовала открывать XML‑файл, но, увидев несколько десятков тысяч строк, закрывала редактор XML и возвращалась страдать в GUI.
SoapUI поддерживает два вида интерфейсов: WSDL для SOAP и его аналог WADL для REST. С точки зрения SoapUI работа с ними мало чем отличается. При импорте интерфейса в проект для каждой точки входа (bindings) создается свой объект interface. Также там сохраняется исходный код WSDL.
В XML‑файле тег имеет несколько атрибутов. В GUI они отображаются в виде Service Properties для WADL и Interface Properties для WSDL.
Атрибуты тега interface для разных интерфейсов
Атрибут | Наличие в WSDL | Наличие в WADL | Комментарий |
wsaVersion | + | - | по умолчанию, "None" |
name | Атрибут name тега binding из WSDL | Имя проекта | |
type | type="wsdl" | type="rest" | указывает на тип интерфейса |
bindingName | Название биндинга с неймспейсом (targetNamespace из wsdl) | - | Пример: bindingName="{http://www.bercut.com/bestbirdinterface}TheBestBirdIsBercutPortBinding" |
soapVersion | "1_1" | - | версия SOAP-протокола |
anonymous | "optional" | - | |
definition | + | - | путь, по которому хранится wsdl, можно указывать с помощью переменных, например, «${#Project#WSDL_TheBestBirdIsBercut}»; путь к файлу на локальном компьютере; адрес хоста, на котором размещен WSDL интерфейс |
xsi:type | "con:WsdlInterface" | "con:RestService" | еще раз обозначаем тип интерфейса, но уже в терминах исходного кода SoapUI |
xmlns:xsi | + | + | |
wadlVersion | - | "http://research.sun.com/wadl/2006/10" | |
basePath | - | "/" | |
definitionUrl | - | + | Путь до проекта wadl |
Если вы редактируете Interface Properties через GUI, то проверяйте сохранение данных. Так, после завершения редактирования параметра Definition нужно снять выделение, кликнув на любое другое Interface Property или пустое поле в этом же разделе. Если вы переключитесь на другой интерфейс и окно Interface Properties закроется, то ваши изменения исчезнут.
Interface имеет несколько дочерних тегов, например, endpoints. Он содержит набор <endpoint> с перечислением адресов, по которым отправляется запрос. Можно указывать как переменными, так и константами.
<con:endpoint>http://localhost:8080</con:endpoint>
<con:endpoint>${#Project#BEST_MOCK_ENDPOINT}</con:endpoint>
В нашем случае отдельные сервисы и методы могли не меняться годами, и интерфейсы в SoapUI‑проектах устаревали. При попытке загрузить свежую версию возникали конфликты. Вручную решить их можно, но при автозапуске желательно минимизировать вероятность их появления.
Управление кэшем осуществляется через настройку Cache WSDLs. В GUI она выставляется на глобальном уровне для всех SoapUI‑проектов пользователя: Preferences → WSDL Settings → Cache WSDLs. Либо на уровне проекта Project Properties — Cache Definition. По умолчанию SoapUI сохраняет кэш интерфейсов в тег definitionCache.
Следующий дочерний тег — operation. Он доступен для WsdlInterface и описывает методы, которые реализует binding. Он используется по принципу «один метод — один тег». Атрибуты bindingOperationName, inputName и outputName заполняются согласно WSDL. Name обычно совпадает с bindingOperationName, type отображает ожидание ответа от операции: «Request‑Response» и «One‑Way». Атрибут action может быть пустой строкой или соответствовать bindingOperationName. Также можно устанавливать атрибуты receivesAttachments, sendsAttachments, anonymous.
При добавлении запросов (New Requests или Ctrl+N) появляется дочерний тег call, у которого, в свою очередь, есть теги settings, encoding (например, UTF-8, значение указывается без кавычек), endpoint, request с текстом запроса, который мы видим в окне с вызовом, credentials, jmsConfig и wsaConfig.
Если в проекте есть два интерфейса А и В, и у каждого их них есть операция с названием C, то при удалении интерфейса А будут также удалены Test Steps операции С интерфейса В.
Пример interface в XML-файле
<con:interface xsi:type="con:WsdlInterface" id="cebbcb47-0866-4ef2-abf8-a84765d58cf9" wsaVersion="NONE" name="SampleServiceSoapBinding" type="wsdl" bindingName="{http://www.soapui.org/sample/}SampleServiceSoapBinding" soapVersion="1_1" anonymous="optional" definition="file:/C:/user_name/SoapUI-Tutorials/SoapUI-Tutorials/WSDL-WADL/sample-service.wsdl" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<con:settings/>
<con:endpoints>
<con:endpoint>http://www.soapui.org/sample</con:endpoint>
</con:endpoints>
<con:operation id="ececa212-81d7-4852-b338-46265d6523e4" isOneWay="false" action="http://www.soapui.org/sample/buy" name="buy" bindingOperationName="buy" type="Request-Response" inputName="" receivesAttachments="false" sendsAttachments="false" anonymous="optional">
<con:settings/>
<con:call id="544622c1-da86-42f2-9a36-52b82da979e7" name="Request 1">
<con:settings/>
<con:encoding>UTF-8</con:encoding>
<con:endpoint>http://www.soapui.org/sample</con:endpoint>
<con:request><![CDATA[<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sam="http://www.soapui.org/sample/">\r
<soapenv:Header/>\r
<soapenv:Body>\r
<sam:buy>\r
<sessionid>?</sessionid>\r
<buystring>?</buystring>\r
</sam:buy>\r
</soapenv:Body>\r
</soapenv:Envelope>]]></con:request>
<con:wsaConfig mustUnderstand="NONE" version="200508" action="http://www.soapui.org/sample/buy"/>
</con:call>
</con:operation>
<con:operation id="2e4fcc2f-4d66-4d86-8485-1b35e4ab2f28" isOneWay="false" action="http://www.soapui.org/sample/login" name="login" bindingOperationName="login" type="Request-Response" inputName="" receivesAttachments="false" sendsAttachments="false" anonymous="optional">
<con:settings/>
<con:call id="7d1bd3d2-bc31-4dd7-babe-8bf447077113" name="Request 1">
<con:settings/>
<con:encoding>UTF-8</con:encoding>
<con:endpoint>http://www.soapui.org/sample</con:endpoint>
<con:request><![CDATA[<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sam="http://www.soapui.org/sample/">\r
<soapenv:Header/>\r
<soapenv:Body>\r
<sam:login>\r
<username>?</username>\r
<password>?</password>\r
</sam:login>\r
</soapenv:Body>\r
</soapenv:Envelope>]]></con:request>
<con:wsaConfig mustUnderstand="NONE" version="200508" action="http://www.soapui.org/sample/login"/>
</con:call>
</con:operation>
</con:interface>
Если вы генерируете проект с нуля или хотите добавить в существующий новый interface, то в XML достаточно будет перечислить operation с атрибутами. Можно указывать не все операции, а только необходимые. Проект корректно откроется и Test Steps с этими методами будут доступны для редактирования. Интерфейс можно будет обновить позже с помощью Add WSDL или Update Definition.
testSuite
Test Suite — это верхний уровень организации тестов. Каждый такой объект хранится как отдельный тег testSuite.
Он имеет всего два атрибута — id и name, но могут встречаться дополнительные, например, disabled.
Первый дочерний тег — settings, затем идет runType — способ запуска тестов и properties. Также появляются setupScript и tearDownScript, значение которых описывается как значение тега, без кавычек и прочего. Обратите внимание, что отступы сохраняются в XML‑файле как есть.
В файле проекта код будет выглядеть вот так (оригинальные отступы сохранены):
<con:tearDownScript>чирик чирик
чирик
</con:tearDownScript>
Рассмотрим интерфейс редактирования TestSuite.
Синим и красным квадратами обозначен выбор способа выполнения runType: SEQUENTIAL («последовательные») и PARALLEL («параллельные») соответственно. В XML‑файле значения записываются большими буквами, без кавычек. Setup Script и TearDown Script позволяют добавить Groovy‑код до и после выполнения Test Suite. В зеленом прямоугольнике выделены доступные для скрипта объекты исходного кода SoapUI, в котором можно получить более подробную информацию о доступных методах, переменных и т. д.
В ходе развития и рефакторинга наших unit‑тестов так сложилось, что мы абстрагировались от названий элементов и стали использовать цепочку Test Suite — Test Case — Test Step в качестве способа группировки. Таким образом у нас появились служебные Test Suite. Их удобно использовать для группировки Test Steps с SOAP‑запросами и другими полезными действиями. Чтобы они не влияли на общий прогон, их можно пометить как disabled. Эта настройка не мешает вызывать их из Groovy‑скриптов.
testCase
testCase — это дочерний тег testSuite. Это минимальный уровень, который можно запустить из Groovy‑скрипта. В дополнение к properties и settings, он содержит перечень тестовых шагов: testStep, loadTest, securityTest.
В редакторе TestCase можно создать новый шаг (строка со значками после TestSteps), запустить его, остановить или зациклить выполнение (иконка круга после красного квадрата остановки — на скриншоте эта настройка включена). Можно отметить функцию запуска тест‑кейса с определенного шага (правая кнопка мыши на нужном testStep → «Run from here»).
Значения полей description, Setup Script и TearDown Script сохраняются в соответствующих тегах: description, setupScript, tearDownScript, а заданные Custom Properties — в структуры properties.
TestCase Editor позволяет настраивать логирование: максимальное количество строк (Max Rows) и наполнение логов. Параметр Follow в testCase не сохраняется:
<con:settings>
<con:setting id="HttpSettings@socket_timeout">34</con:setting>
<con:setting id="com.eviware.soapui.impl.wsdl.panels.testcase.JTestRunLog$OptionsForm@max_rows">1000</con:setting>
<con:setting id="com.eviware.soapui.impl.wsdl.panels.testcase.JTestRunLog$OptionsForm@errors_only">true</con:setting>
</con:settings>
Тег setting с id = HttpSettings@socket_timeout указывает на настройку Socket timeout в TestCase Options (TestCase Editor → значок шестеренки). Остальные параметры хранятся в атрибутах тега testCase.
Соответствие атрибутов в XML и настроек в GUI
Атрибут тега testCase | Настройка в GUI |
failOnError | Basic/Abort on Error |
failTestCaseOnErrors | Basic/Fail TestCase on error |
keepSession | Basic/Session |
maxResults | Basic/Max Result |
searchProperties | Basic/Search Properties |
discardOkResults | Basic/Discard OK Result |
timeout | Basic/TestCase timeout |
wsrmEnabled | WS-RM/WS-RM Enabled |
wsrmVersion | WS-RM/WS-RM Version |
wsrmAckTo | WS-RM/WS-RM Ack To |
wsrmExpires | WS-RM/WS-RM Expires |
amfAuthorisation | AMF/AMF Session |
amfEndpoint | AMF/endpoint |
amfLogin | AMF/login |
amfPassword | AMF/password |
Если вы запускаете проекты через командную строку, то следите, чтобы названия Test Suite не повторялись, так как они будут перезаписывать результаты выполнения друг друга. Также SoapUI автоматически заменяет пробелы на нижние подчеркивания и удаляет такие знаки, как «‑», «=» и другие.
testStep
Всего SoapUI «из коробки» предоставляет 14 видов testStep. Они представлены на скриншоте:
В меню Add Step встречаются Drop MQTT Connection, Publish using MQTT, Receive MQTT Message — они по умолчанию не поддерживаются. Если попытаться их создать без дополнительной настройки окружения, то в XML‑файле успешно сохранится новый testStep, но проект в GUI открываться перестанет.
Каждый testStep имеет 4 атрибута: type, name, disabled, id и дочерние теги settings, config, может встречаться description. В зависимости от вида элемента меняется наполнение тега config. Например, для Groovy Script указывается атрибут type=groovy и он имеет один дочерний config/script.
Для сравнения, SOAP Request имеет более сложную структуру
<con:testStep type="request" name="SOAP Request" id="7d1a1663-6ddc-401b-9327-a172dc37d990">
<con:settings/>
<con:config xsi:type="con:RequestStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<con:interface>BirdManagementPortBinding</con:interface>
<con:operation>getBird</con:operation>
<con:request name="SOAP Request" id="8d5cc343-b5f5-4bbe-8e40-5770e6c3ddbf" outgoingWss=""
incomingWss="" useWsAddressing="true" useWsReliableMessaging="true">
<con:encoding>UTF-8</con:encoding>
<con:endpoint>${#Project#MOCK_BirdManagement}</con:endpoint>
<con:request><![CDATA[<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:bird="http://www.bercut.com/cc/sa/schema/birdmanagement" xmlns:com="http://www.bercut.com/schema/commonInVoiceAPI">\r
<soapenv:Header/>\r
<soapenv:Body>\r
<bird:getBird>\r
<bird:birdSpecies>беркут</bird:birdSpecies>\r
<!--1 or more repetitions:-->\r
<!--Optional:-->\r
<acc:lifeTime>?</acc:lifeTime>\r
</acc:getBird>\r
</soapenv:Body>\r
</soapenv:Envelope>]]></con:request>
<con:assertion type="SOAP Response" id="563120f0-a5a7-40cf-b3c6-06c8e211fbe9"/>
<con:assertion type="Schema Compliance" id="21bc2d30-31a4-4560-ba40-b96ab6944a78">
<con:configuration/>
</con:assertion>
<con:assertion type="SOAP Fault Assertion" id="d0fa610b-ae31-4852-9a19-921a00f9ec64"/>
<con:assertion type="XPath Match" id="15f8825c-1f1f-4430-816e-0f62ca27287e" name="XPath Match">
<con:configuration>
<path>//*[local-name()='birdSpecies'][1]/*[local-name()='birdSpeciesId']</path>
<content>1</content>
<allowWildcards>false</allowWildcards>
<ignoreNamspaceDifferences>false</ignoreNamspaceDifferences>
<ignoreComments>false</ignoreComments>
</con:configuration>
</con:assertion>
<con:credentials>
<con:username>admin</con:username>
<con:password>admin</con:password>
<con:domain>adminadmin</con:domain>
<con:selectedAuthProfile>Basic</con:selectedAuthProfile>
<con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes>
<con:authType>Global HTTP Settings</con:authType>
</con:credentials>
<con:attachment>
<con:name>текст_выступления.docx</con:name>
<con:contentType>application/octet-stream</con:contentType>
<con:contentId>текст_выступления.docx</con:contentId>
<con:url>C:/bird_go_to_party/текст_выступления.docx</con:url>
<con:id>a4f18f0b-0092-463c-90cf-cd4c285ccce7</con:id>
</con:attachment>
<con:jmsConfig JMSDeliveryMode="PERSISTENT"/>
<con:jmsPropertyConfig/>
<con:wsaConfig mustUnderstand="TRUE" version="200508" addDefaultAction="true"/>
<con:wsrmConfig version="1.2"/>
</con:request>
</con:config>
</con:testStep>
Обратите внимание, что пароли из Creditails обычно хранятся в открытом виде, даже если в GUI они скрыты за точками.
loadTest
LoadTest предназначен для нагрузочного тестирования существующих Test Steps.
Таблица соответствия GUI и XML
GUI | XML | Комментарии |
Threads | threadCount | |
LoadTest Options/Sample Interval | sampleInterval | |
LoadTest Options/Thread Setup Delay | startDelay | |
LoadTest Options/"Calculate TPS/BPS" | calculateTPSOnTimePassed | true/false |
LoadTest Options/Reset Statistics | resetStatisticsOnThreadCountChange | |
LoadTest Options/Disable History | historyLimit | 0 соответствует отмеченной галочке, другие значения - не отмеченной, по умолчанию = -1 |
Limit (первое окошко с числами) | testLimit | |
Limit (второе окошко с типом лимита) | limitType | Например, COUNT_PER_THREAD |
Strategy | loadStrategy/type | Например, Simple |
Test Delay | loadStrategy/config/testDelay | |
Random | loadStrategy/config/randomFactor | В GUI отображается без точки, в XML с точкой, например, 43.0 |
LoadTest Options/Max Assertions in Log | maxAssertionErrors | |
LoadTest Options/вкладка Statistics Log/Log Folder | statisticsLogFolder | |
LoadTest Options/вкладка Statistics Log/Log Interval | statisticsLogInterval | по умолчанию, 0 |
LoadTest Options/вкладка Statistics Log/Log on ThreadCount change | logStatisticsOnThreadChange | true/false |
LoadTest Options/Cancel Running | cancelOnReachedLimit | |
LoadTest Options/Cancel Excessive | cancelExcessiveThreads | true/false |
Strategy Interval | strategyInterval | |
LoadTest Options/TestStep Statistics | updateStatisticsPerTestStep | true/false |
В теге часто используются дочерние settings, например, для управления настройками HTTP:
<con:settings>
<con:setting id="HttpSettings@include_request_in_time_taken">false</con:setting>
<con:setting id="HttpSettings@include_response_in_time_taken">false</con:setting>
<con:setting id="HttpSettings@close-connections">true</con:setting>
</con:settings>
И вот такая настройка для loadTest типа request:
<con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting>
В GUI эти настройки можно найти в разделе Options у testStep (например, ctrl+shift+O или правая кнопка мыши на нужном testStep → Options, или правая кнопка мыши на нужном testStep → Show Editor → шестерёнка).
Assertion в LoadStep представляют собой теги assertion с атрибутами type (в GUI он выбирается при добавлении нового assertion) и name. Также разные типы таких проверок могут содержать дополнительные структуры, например:
<con:assertion type="Step Status" name="Step Status"/>
<con:assertion type="Step Maximum" name="Step Maximum">
<con:configuration>
<name>Step Max Maximum</name>
<min-requests>100</min-requests>
<max-value>1000</max-value>
<test-step>start mock</test-step>
<max-errors>70</max-errors>
</con:configuration>
</con:assertion>
securityTest
Позволяет применять различные сценарии тестирования на безопасность к существующим testStep.
В разделе SecurityTest Options представлены всего 2 настройки: Abroad on Error и Fail SecurityTest on Error. Они связаны с атрибутами failOnError и failSecurityTestOnScanErrors. Также присутствует атрибут name.
В дочернем теге settings описываются настройки логирования (Log Options):
<con:settings>
<con:setting id="com.eviware.soapui.security.log.JSecurityTestRunLog$OptionsForm@max_rows">
154
</con:setting>
<con:setting id="com.eviware.soapui.security.log.JSecurityTestRunLog$OptionsForm@errors_only">
false
</con:setting>
</con:settings>
В редакторе теста на безопасность перечислены все testStep из testCase, в котором расположен SecurityTest. При этом недоступные для тестирования объекты обозначены светло‑серым цветом.
Больше всего вариантов готовых сценариев у SOAP Request, затем идет REST Request, HTTP Request, в самом конце — GraphQL Request без подготовленных сценариев, только пользовательские (Custom).
securityTest включает в себя набор testStepSecurityTest по одному для каждого тестируемого testStep.
Пример наполнения testStepSecurityTest
<con:testStepSecurityTest>
<con:testStepId>7d1a1663-6ddc-401b-9327-a172dc37d990</con:testStepId>
<con:testStepSecurityScan type="FuzzingScan" name="Fuzzing Scan" id="c679ee72-ac05-4495-b22e-1392079095da" applyForFailedStep="false" disabled="false" runOnlyOnce="true">
<con:settings/>
<con:config xsi:type="con:FuzzerScan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<con:minimal>5</con:minimal>
<con:maximal>15</con:maximal>
<con:numberOfRequest>100</con:numberOfRequest>
</con:config>
<con:testStep/>
<con:checkedParameters/>
<con:executionStrategy xsi:nil="true" immutable="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<con:strategy>ALL_AT_ONCE</con:strategy>
<con:delay>100</con:delay>
</con:executionStrategy>
</con:testStepSecurityScan>
</con:testStepSecurityTest>
Первым дочерним тегом для testStepSecurityTest будет testStepId со значением атрибута ID из связанного тега testStep. После идентификатора идет перечень testStepSecurityScan. Если вы планируете исправлять SoapUI‑проект или генерировать его с нуля, обратите внимание на согласованность этих ID.
XML-атрибуты testStepSecurityTest и GUI
XML | GUI | Комментарии |
type | Выбирается при добавлении нового SecurityScan | Тип применяемого сценария сканирования, например, SQLInjectionScan |
name | Отображаемое имя | через GUI его изменить нельзя |
applсyForFailedStep | Apply to Failed TestSteps на вкладке Strategy | |
runOnlyOnce | Run only once на вкладке Strategy |
Оставшиеся настройки из этой вкладки в XML записываются в дочерний для testStepSecurityScan тег executionStrategy, у которого в свою очередь есть тег strategy - Select strategy (значением может быть ONE_BY_ONE или ALL_AT_ONCE), delay - Request Delay (ms).
Первый дочерний тег settings пустой, затем идет config с перечислением настроек, которые требуются выбранному сценарию. У него в атрибутах также указывается type, но в этот раз добавляется неймспейс:
<con:config xsi:type="con:FuzzerScan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<con:minimal>5</con:minimal>
<con:maximal>15</con:maximal>
<con:numberOfRequest>100</con:numberOfRequest>
</con:config>
Для каждого сценария используется свой набор параметров, в GUI они расположены на вкладке Advanced в редакторе Security Scan. Для некоторых Security Scan их может не быть в принципе, но в XML все равно должен быть тег config. Например, для Boundary Scan:
<con:config xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
Встречающийся в SoapUI‑проекте атрибут xsi:nil и его неймспейс xmlns:xsi=http://www.w3.org/2001/XMLSchema‑instance для заполнения не обязательны и их можно проигнорировать.
Дочерний по отношению к testStepSecurityTest тег testStep появляется при создании Security Scan, не связан с элементами в GUI (по крайней мере, мне эту связь установить не удалось). SoapUI корректно открывает проект, если этого тега нет.
Следующий дочерний тег — checkedParameters. Он соответствует разделу Parameters в настройках SecurityScan, если они предусмотрены для выбранного типа.
Соответствие XML и GUI
атрибут в XML | отображение в GUI |
label | Label |
parameterName | Name |
xpath | XPath |
checked | Enabled |
Например, для скриншота выше XML будет выглядеть следующим образом:
<con:checkedParameters>
<con:parameters label="tets" parameterName="Username" xpath="" checked="true"/>
</con:checkedParameters>
В редакторе Security Scan на вкладке Assertions каждая строка является тегом assertion в XML. В его атрибутах указывается type, например, «Invalid HTTP Status Codes». name по умолчанию совпадает с типом, в GUI assertion можно переименовать через кнопку F2. Индивидуальные настройки каждого типа assertion перечисляются в дочерних тегах codes:
<con:assertion type="Invalid HTTP Status Codes" id="f6ead976-b5f0-44e6-a69c-f6701f2207a5"
name="Invalid HTTP Status Codes For Kittens">
<con:configuration>
<codes>505</codes>
</con:configuration>
</con:assertion>
mockService
Чтобы MockService гарантированно подтянул изменения, нужно:
Если mock запущен, то остановить его.
Сохранить проект.
Запустить mock снова.
MockService отвечает за эмуляцию работы сторонних сервисов, в XML он обозначается как тег mockService. Бывает двух видов: restMockService (для WADL‑интерфейсов) и mockService (для WSDL‑интерфейсов), основное отличие — это тип операций: restMockAction для REST и mockOperation для SOAP. Можно добавлять несколько интерфейсов.
Соответствие параметров mockService в XML и GUI
XML атрибут | отображение в GUI | Комментарии |
path | Path | |
port | Port | требует прямого указания порта, то есть в XML-файле нельзя указать "${#Project#MOCK_PORT}" - нужно "8080" |
host | Host | можно настраивать через переменные или напрямую |
bindToHostOnly | Host Only | |
docroot | Docroot | |
faultMockOperation | Fault Operation | При создании нового mock атрибут faultMockOperation будет отсутствовать и появится только после того, как вы заполните соответствующее поле |
name | наименование MockService | |
dispatchResponseMessages | MockService Properties "Dispatch Responses" | в XML появляется, только когда параметр изменяется в GUI |
<con:mockService port="8089" path="/" host="${#Project#MOCK_IP}" name="MockService_bird" bindToHostOnly="false" docroot="" faultMockOperation="extinctBird" dispatchResponseMessages="true">
Настройки «Match SOAP Version», «Require SOAP Action» и количество строк логов хранятся в теге settings.
<con:settings>
<con:setting id="com.eviware.soapui.impl.wsdl.mock.WsdlMockService@require-soap-action">true</con:setting>
<con:setting id="com.eviware.soapui.impl.wsdl.mock.WsdlMockService@require-soap-version">true</con:setting>
<con:setting id="com.eviware.soapui.impl.wsdl.panels.mock.WsdlMockServiceDesktopPanel$LogListModel@maxSize">50</con:setting>
</con:settings>
mockService имеет тег properties для хранения своих Custom Properties и тег description, который можно задать через GUI в MockService Properties → Description или в MockService Editor → description.
Скриптам из вкладок «Start Script», «Stop Script», «OnRequest Script», «AfterRequest Script» соответствуют теги startScript, stopScript, onRequestScript, afterRequestScript. «OnRequest Script» и «AfterRequest Script» применяются, когда на mock приходит запрос. Бывает полезно добавить в «OnRequest Script» логирование запроса:
log.info(this.mockRequest.getHttpRequest());
log.info(this.mockRequest.getRequestHeaders());
log.info(this.mockRequest.getRequestContent());
В зависимости от количества интерфейсов и операций, которые использованы в MockService, время запуска mock может увеличиваться. Например, вы видите в логах, что ваш mock успешно запустился, но при попытке к нему обратиться, возвращается ошибка: Cannot invoke "javax.wsdl.BindingOperation.getExtensibilityElements()" because "bindingOperation" is null.
В наших проектах обычно хватало паузы в 6 секунд, а для особенно больших mock — 12 или 24 секунд.
mockOperation
mockOperation — основной тег, ради которого и создают mockService. В нем содержатся ответы на запросы для SOAP‑операций, которые могут принадлежать разным WSDL‑интерфейсам. Для создания тега необходимо указать атрибуты: наименование — name, соответствующий интерфейс — interface, название мокирующейся операции operation.
Способ, по которому будет определяться возвращаемый ответ, указывается в теге dispatchStyle. Для способов XPATH и SCRIPT дополнительно появляется тег dispatchPath.
Дочерними тегами будут:
settings
defaultResponse
dispatchStyle — способ, по которому будет определяться возвращаемый ответ
dispatchPath — заполняется значением из окна под зеленой стрелочкой, если dispatchStyle XPATH или SCRIPT
dispatchConfig — заполняется, если dispatchStyle = QUERY_MATCH
Самый важный дочерний тег — defaultResponse. Он должен быть обязательно заполнен названием существующего mockResponse, иначе будет возникать ошибка com.eviware.soapui.impl.wsdl.mock.DispatchException: Failed to find MockResponse. Она может появиться как из‑за неправильной генерации проекта внешними утилитами, так и при работе самого SoapUI.
Дочерние теги для defaultResponse:
name — название условия выбора
query — выражение XPath
match — значение, которому должен соответствовать результат выполнения query (Expected Value)
response — название mockResponse, который нужно вернуть (Dispatch to)
disabled
Скриншот выше соответствует следующему коду:
<con:dispatchConfig xsi:type="con:MockOperationQueryMatchDispatch"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<con:query>
<con:name>11</con:name>
<con:query>123</con:query>
<con:match>true</con:match>
<con:response>Response 6</con:response>
<con:disabled>true</con:disabled>
</con:query>
<con:query>
<con:name>success</con:name>
<con:query>//*[local-name()='code'] = 'create_new_buy'</con:query>
<con:match>true</con:match>
<con:response>Response 6</con:response>
<con:disabled>true</con:disabled>
</con:query>
</con:dispatchConfig>
Дочерний тег response состоит из имени (name), httpResponseStatus (по умолчанию 200, может возвращать 500), encoding (по умолчанию «UTF-8»). Среди дочерних тегов к response есть settings, script, responseContent непосредственно с текстом запроса, например, SOAP‑запрос будет обернут в <![CDATA[...]]>, в GUI же будет отображаться только сам запрос.
Также дочерним к response будет тег wsaConfig:
<con:wsaConfig mustUnderstand="NONE" version="200508" action="http://www.bercut.com/BirdLifeCycle/endBirdLifeResponse"/>
Здесь важно отметить атрибут action, в зависимости от загруженного wsdl он может быть и полным путем до элемента‑ответа операции, а может иметь только ее название. Больше разных настроек можно найти на вкладке WS‑A.
Атрибут version в GUI представлен как выпадающий список из двух значений: 200 508 и 200 408. Если же обойти ограничения интерфейса и прописать другой номер версии в SOAP‑проекте, то ваш mockResponse перестанет открываться, как минимум на редактирование. В логах можно будет найти ошибку:
com.jgoodies.binding.beans.PropertyAccessException: Failed to read an adapted Java Bean property.cause=org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException: string value '2005008' is not a valid enumeration value for WsaVersionType in namespace http://eviware.com/soapui/config
В графическом интерфейсе флаг «Enable WS‑A addressing» указывает на то, используются ли настройки из этой секции (WS‑A).
Иногда атрибуты тега wsaConfig удаляются из файла XML.
Кроме тела ответа, можно также добавлять скрипты:
import javax.xml.xpath.*
import groovy.xml.StreamingMarkupBuilder
import groovy.lang.Binding;
import groovy.lang.Script;
def util = new com.eviware.soapui.support.GroovyUtils( context )
def xml = new XmlSlurper().parseText(mockRequest.requestContent)
def v_transact_id = xml.Header.TransactionIdentifier
// create XmlHolder for request content
def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
// get target testcase
def mock_service = mockResponse.mockOperation.mockService.project
def testCase = mock_service.testSuites["TransactionProcess"].testCases['Rollback']
testCase.setPropertyValue("transact_id", v_transact_id.toString())
def runner = testCase.run( new com.eviware.soapui.support.types.StringToObjectMap(), false )
В примере сначала из запроса к mock получают параметр TransactionIdentifier. Затем через переменную mockResponse осуществляется доступ к объекту SoapUI‑проекта, перемещаются на уровень testSuite и находят определённый testCase, в котором сформирован SOAP‑запрос. После этого заполняются переменные и запускается testCase. В результате, перед отправкой ответа, mock отправляет дополнительный запрос к внешней системе. Такой подход можно использовать, если системы требуют параллельных действий помимо основного запроса‑ответа.
restMockAction
По структуре restMockAction похож на mockOperation, но имеет меньший набор вариантов для выбора ответа: dispatchStyle может быть или SEQUENCE, или SCRIPT и другие атрибуты: name (по умолчанию, совпадает с resourcePath), method — название http метода, resourcePath — часть url, по которому располагается операция, например, «/accountcreation», id.
В атрибутах дочернего тега response появляется mediaType с информацией о структуре возвращаемых данных. В GUI для его определения предлагается раскрывающийся список, но при этом нет защиты от неправильных значений. Если поменять в XML‑файле его значение на набор неизвестных символов, то SoapUI корректно откроет проект.
В остальном атрибуты совпадают с mockOperation. В дочерних тегах отсутствует информация для WSA, но есть тег header для заполнения HEADER в HTTP.
SoapUI и Groovy
Во многих элементах поддерживаются скрипты на Groovy, начиная с testCase и выше по дереву элементов. Он помогает создавать более сложные проверки, автоматически запускать элементы проекта и т. д. Скрипты чаще всего называются Setup Script или TearDown Script, само слово «Script» указывает на возможность использовать Groovy.
В верхнем правом углу редактора указаны переменные, которые доступны из скрипта. В исходном коде можно найти больше информации про них, а также их связь с другими более крупными объектами.
В официальной документации или вот тут можно найти полезные примеры скриптов.
Если вносите правки в XML‑файл тестов, особенно, когда применяете форматирование, следите, чтобы структура Groovy‑кода не поменялась, так как SoapUI применяет скрипты как написано в XML‑файле со всеми пробелами, отступами и т. д.
Пример скрипта, который получает SOAP‑ответ из testStep, парсит его по тегу variable. Далее для каждого тега он собирает дополнительную информацию и вызывает с ними testCase. Такой сценарий можно использовать, когда настройки хранятся на отдельном сервисе, в процессе тестирования они меняются и хочется быть уверенным, что после прогона стенд вернется к изначальному состоянию.
import javax.xml.xpath.*
import groovy.xml.StreamingMarkupBuilder
import groovy.lang.Binding;
import groovy.lang.Script;
def util = new com.eviware.soapui.support.GroovyUtils( context )
def testCase = testRunner.testCase
def testStepSetValue = testCase.getTestStepByName("setValue")
def resp = testCase.testSuite.testCases['Start_mock'].getTestStepByName("getGroupWithType").getProperty("Response")
def xmlResp = util.getXmlHolder(resp.value.toString())
for(v=1;v<Integer.parseInt(xmlResp["count(//variable)"]);v++){
def opName = xmlResp["//variables/variable[$v]/name"];
def opType = xmlResp["//variables/variable[$v]/type"];
def opValue = testCase.testSuite.getProperty(opName).value;
testCase.setPropertyValue("currOpParamName", opName );
testCase.setPropertyValue("currOpParamType", opType );
testCase.setPropertyValue("currOpParamValue", opValue);
testStepSetValue.run(testRunner, context);
}
В SoapUI много мест, где можно использовать Groovy. Наша практика показала, что удобнее использовать дополнительные testSuite/testCase/testSteps вместо скриптов: логирование проще и понятнее, ошибки записываются в отчет о тестировании. А главное, такие проекты удобнее поддерживать — не нужно просматривать каждый объект, в попытке найти то самое место, которое ведет себя неправильно. Элементы test* видны сразу и, если они имеют хорошее название, то считываются быстрее. Также с ними удобнее управлять прогоном, включая и выключая необходимые.
Заключение
Работа над статьей действительно оказалась «приключением на 20 минут»: на её написание у меня ушло более года. В основе материала лежит опыт, который я приобрела в процессе рефакторинга и автоматизации проектов SoapUI. Изначально обе части представляли собой единый текст, но объем оказался слишком большим, и, в конечном итоге, было удобнее разделить их на две статьи. Несмотря на масштабность работы, мне не удалось охватить все аспекты, которые хотелось бы отразить.
Я старалась добавить как можно больше перекрестных ссылок для удобства навигации, чтобы читателям было проще ориентироваться в тексте. Вторую часть сложнее воспринимать как единый текст, но она окажется полезной, если обращаться к конкретным разделам.
Что касается автоматизации юнит‑тестирования: теперь при каждом мерж‑реквесте автоматически поднимается стенд в Docker, запускаются тесты, а их результаты отправляются в корпоративный мессенджер. Кроме того, мы разработали инструмент для генерации шаблонов новых SoapUI‑проектов. Задача выполнена.