Jenkins-SoapUI連携(API自動テスト)

JenkinsからSoapUIのテストケースを自動実行する方法を備忘録として残しておきます。

概要

Jenkinsのワークスペースを利用したほうが簡単なので、Gitリポジトリを経由しています。Gitに登録したSoapUIのプロジェクトをJenkinsから取出し、それをコマンド実行します。

準備

テスト対象のWebサービス(REST,SOAP)をテストするようなテストケースをSoapUIで作成し、プロジェクトとして保存します。

本来は自作Webサービスのテストを行うためですが、Jenkins連携が目的なので、一般公開されているお天気Webサービスを活用させていただきました。

※テストケースの作成方法は割愛します。他の記事や、公式ページを参考にしてください。

Assertionエラーになるようなテストケースをわざと仕込んでおきます。テストケース一度実行して「Select from current」で正常になったものに対して、「Expected Result」の値を変更するのが簡単です。

テストケースの作成が終わったらプロジェクトを保存します。今回の例では「Wether-soapui-project.xml」としてファイル保存しました。

Gitリポジトリへの登録

Jenkinsから扱いやすいように、作成したSoapUIプロジェクトをGitリポジトリに登録します。

※Gitについての説明は割愛します。他の記事を参考にしてください。

Wether-soapui-project.xml

SoupUIのプロジェクトは以下のような定義です。※重複するgroovy scriptを削除したものを掲載しています。

<?xml version="1.0" encoding="UTF-8"?>
<con:soapui-project id="2ba5b981-344f-4fe3-acc8-8eb9f933cdc6" activeEnvironment="Default" name="Wether" resourceRoot="" soapui-version="5.5.0" abortOnError="false" runType="SEQUENTIAL" xmlns:con="http://eviware.com/soapui/config"><con:settings/><con:interface xsi:type="con:RestService" id="3a0fff65-ffc7-42e2-8f0a-e334bb7c08af" wadlVersion="http://wadl.dev.java.net/2009/02" name="http://weather.livedoor.com" type="rest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><con:settings/><con:definitionCache type="TEXT" rootPart=""/><con:endpoints><con:endpoint>http://weather.livedoor.com</con:endpoint></con:endpoints><con:resource name="V1" path="/forecast/webservice/json/v1" id="6440b982-a3ec-485e-88f1-196d15e33a94"><con:settings/><con:parameters><con:parameter><con:name>city</con:name><con:value>130010</con:value><con:style>QUERY</con:style><con:default>130010</con:default><con:path xsi:nil="true"/><con:description xsi:nil="true"/></con:parameter></con:parameters><con:method name="Today" id="0a44e056-9e24-4d7a-973c-f94254ba9526" method="GET"><con:settings/><con:parameters/><con:representation type="RESPONSE"><con:mediaType>application/json; charset=utf-8</con:mediaType><con:status>200</con:status><con:params/><con:element xmlns:v1="http://weather.livedoor.com/forecast/webservice/json/v1">v1:Response</con:element></con:representation><con:request name="Tokyo" id="d6cead28-dab4-4d13-9053-fb74673be350" mediaType="application/json"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting></con:settings><con:endpoint>http://weather.livedoor.com</con:endpoint><con:request/><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters><entry key="city" value="130010" xmlns="http://eviware.com/soapui/config"/></con:parameters><con:parameterOrder><con:entry>city</con:entry></con:parameterOrder></con:request></con:method></con:resource></con:interface><con:testSuite id="c803679e-ae77-4693-9aa2-29f8162074da" name="TestSuite"><con:description>TestSuite generated for REST Service [http://weather.livedoor.com]</con:description><con:settings/><con:runType>SEQUENTIAL</con:runType><con:testCase id="b0c70c7c-b817-4777-9486-918f7aacfc97" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="TestCase01" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""><con:description>TestCase generated for REST Resource [V1] located at [/forecast/webservice/json/v1]</con:description><con:settings/><con:testStep type="restrequest" name="Tokyo" id="e6c6617a-48fa-40e0-9282-eda0e31fa204"><con:settings/><con:config service="http://weather.livedoor.com" resourcePath="/forecast/webservice/json/v1" methodName="Today" xsi:type="con:RestRequestStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><con:restRequest name="Tokyo" id="d6cead28-dab4-4d13-9053-fb74673be350" mediaType="application/json"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting></con:settings><con:endpoint>http://weather.livedoor.com</con:endpoint><con:request/><con:originalUri>http://weather.livedoor.com/forecast/webservice/json/v1</con:originalUri><con:assertion type="JsonPath Match" id="8d67ecaa-723a-4aae-8461-73be7f261e80" name="JsonPath Match"><con:configuration><path>$.forecasts[0].telop</path><content>晴のち曇</content><allowWildcards>false</allowWildcards><ignoreNamspaceDifferences>false</ignoreNamspaceDifferences><ignoreComments>false</ignoreComments></con:configuration></con:assertion><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters/><con:parameterOrder><con:entry>city</con:entry></con:parameterOrder></con:restRequest></con:config></con:testStep><con:testStep type="groovy" name="LoggerJSON" id="91991aeb-ebb3-45fc-9d95-126c41e9bcac"><con:settings/><con:config><script>// TestCaseName
def testCaseName = testRunner.testCase.name
// Date format
def fileNameDate = new Date().format("yyyyMMddHHmmss")
def resultListFileNameDate = new Date().format("yyyyMMddHH")

// TestTime
def testTime = new Date()
// Log File Path
def dir =  testRunner.testCase.testSuite.project.getPropertyValue("outLogPath")
// Log File Name
def fileName = "${testCaseName}_${fileNameDate}.log"
def testCaseFilePrefix = testRunner.testCase.testSuite.getPropertyValue("resultListFilePrefix")
def testResultListCsvFileName = "${testCaseFilePrefix}_${resultListFileNameDate}.csv"
def testAssertionFIlePrefix = testRunner.testCase.testSuite.getPropertyValue("assertionListFilePrefix")
def testAssertionListCsvFileName = "${testAssertionFIlePrefix}_${resultListFileNameDate}.csv"

// Output File
def outputFile = new File(dir, fileName)
def testResultListFile = new File(dir, testResultListCsvFileName)
def testAssertionListFile = new File(dir, testAssertionListCsvFileName)

// Log title
outputFile.append("########################################\n")
outputFile.append("# " + testCaseName + "\n")
outputFile.append("########################################\n")

// Test time
outputFile.append("Test time: " + testTime + "\n")

// Current Step Index
def currentStepIdx = context.currentStepIndex
// Previous Step
def previousStep = testRunner.testCase.getTestStepAt(currentStepIdx -1 )
outputFile.append("\n")

// Request message
outputFile.append("### Request message ###\n")
outputFile.append(previousStep.testRequest.messageExchange.rawRequestData)
outputFile.append("\n\n")

// Response message
outputFile.append("### Response message ###\n")
outputFile.append(previousStep.testRequest.messageExchange.rawResponseData)
outputFile.append("\n")

// Step Index
def stepIdx = 0
// Assertion Index
def assertionIdx = 0

// testRunner size
def resCount = testRunner.results.size();
//def resCount = testRunner.resultCount;

// Test Target
def testTarget = ""

// Get testCase result
for (testCaseResult in testRunner.results){
    // Step Index
    stepIdx = stepIdx +1

    // Judge Test Target
    if (resCount==stepIdx){
        testTarget = "Target"
    } else {
        testTarget = "NotTarget"
    }
    testResultListFile.append("${testCaseName}")
    testResultListFile.append(","+stepIdx)
    testResultListFile.append(","+testTarget)
    testResultListFile.append(","+testCaseResult.getStatus().toString())
    testResultListFile.append("\n")

    // Initialize Assertion Index
    assertionIdx = 0
    // Get Assertion List
    for (assertion in testCaseResult.getTestStep().getAssertionList()) {
        // Increment
        assertionIdx = assertionIdx +1
        // Assertion Result
        testAssertionListFile.append("${testCaseName}")
        testAssertionListFile.append(","+stepIdx)
        testAssertionListFile.append(","+testTarget)
        testAssertionListFile.append(","+assertionIdx)
        testAssertionListFile.append(","+testCaseResult.getTestStep().name)
        testAssertionListFile.append(","+assertion.getLabel())
        testAssertionListFile.append(","+assertion.getStatus())
        testAssertionListFile.append(","+assertion.getErrors())
        testAssertionListFile.append("\n")
   }
}
</script></con:config></con:testStep><con:testStep type="groovy" name="SaveResponseJson" id="8f7b8356-547e-496f-ae52-26b081981593"><con:settings/><con:config><script>// JsonSlurper
import groovy.json.JsonSlurper;

// Current Step Index
def currentStepIdx = context.currentStepIndex;
// Previous Step
def previousStep = testRunner.testCase.getTestStepAt(currentStepIdx -2 );

// Response message
def resMsg = new String(previousStep.testRequest.response.contentAsString);
def jSlurper = new groovy.json.JsonSlurper().parseText(resMsg);
def name = "${jSlurper.name}";

// Save Response
if (name == "") {
    testRunner.testCase.testSuite.setPropertyValue("resName","Name Error!")
} else {
    testRunner.testCase.testSuite.setPropertyValue("resName",name)
}
</script></con:config></con:testStep><con:properties/></con:testCase><con:testCase id="eb083c40-875f-43f8-92c9-bf8b0b3f11c6" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="TestCase02" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""><con:description>TestCase generated for REST Resource [V1] located at [/forecast/webservice/json/v1]</con:description><con:settings/><con:testStep type="restrequest" name="Osaka" id="661c710c-217d-4fd7-8760-ad171f6e6802"><con:settings/><con:config service="http://weather.livedoor.com" resourcePath="/forecast/webservice/json/v1" methodName="Today" xsi:type="con:RestRequestStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><con:restRequest name="Osaka" id="d6cead28-dab4-4d13-9053-fb74673be350" mediaType="application/json"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting></con:settings><con:endpoint>http://weather.livedoor.com</con:endpoint><con:request/><con:originalUri>http://weather.livedoor.com/forecast/webservice/json/v1</con:originalUri><con:assertion type="JsonPath Match" id="8d67ecaa-723a-4aae-8461-73be7f261e80" name="JsonPath Match"><con:configuration><path>$.forecasts[0].telop</path><content>曇のち雨</content><allowWildcards>false</allowWildcards><ignoreNamspaceDifferences>false</ignoreNamspaceDifferences><ignoreComments>false</ignoreComments></con:configuration></con:assertion><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters><entry key="city" value="270000" xmlns="http://eviware.com/soapui/config"/></con:parameters><con:parameterOrder><con:entry>city</con:entry></con:parameterOrder></con:restRequest></con:config></con:testStep><con:properties/></con:testCase><con:testCase id="99fb9d64-fc11-498c-9112-1f12de9db76d" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="TestCase03" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""><con:description>TestCase generated for REST Resource [V1] located at [/forecast/webservice/json/v1]</con:description><con:settings/><con:testStep type="restrequest" name="OkinawaNaha" id="ce179131-5e2f-481d-8e9c-7d868ed4cfdc"><con:settings/><con:config service="http://weather.livedoor.com" resourcePath="/forecast/webservice/json/v1" methodName="Today" xsi:type="con:RestRequestStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><con:restRequest name="OkinawaNaha" id="d6cead28-dab4-4d13-9053-fb74673be350" mediaType="application/json"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers">&lt;xml-fragment/></con:setting></con:settings><con:endpoint>http://weather.livedoor.com</con:endpoint><con:request/><con:originalUri>http://weather.livedoor.com/forecast/webservice/json/v1</con:originalUri><con:assertion type="JsonPath Match" id="8d67ecaa-723a-4aae-8461-73be7f261e80" name="JsonPath Match"><con:configuration><path>$.forecasts[0].telop</path><content>曇</content><allowWildcards>false</allowWildcards><ignoreNamspaceDifferences>false</ignoreNamspaceDifferences><ignoreComments>false</ignoreComments></con:configuration></con:assertion><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters><entry key="city" value="471010" xmlns="http://eviware.com/soapui/config"/></con:parameters><con:parameterOrder><con:entry>city</con:entry></con:parameterOrder></con:restRequest></con:config></con:testStep><con:properties/></con:testCase><con:properties><con:property><con:name>resultListFilePrefix</con:name><con:value>result</con:value></con:property><con:property><con:name>assertionListFilePrefix</con:name><con:value>assert</con:value></con:property><con:property><con:name>resName</con:name><con:value>null</con:value></con:property></con:properties></con:testSuite><con:properties><con:property><con:name>outLogPath</con:name><con:value>C:\logs\SoupUI</con:value></con:property></con:properties><con:wssContainer/><con:oAuth2ProfileContainer/><con:oAuth1ProfileContainer/><con:sensitiveInformation/></con:soapui-project>

Gitローカルリポジトリ例

Gitベアリポジトリ例

Gitリポジトリへの登録が終わったら、Jenkinsから取り出せるようにします。

Jenkinsジョブ設定

「ソースコード管理」にて、GitのリポジトリURLはベアリポジトリを指定しました。

ビルド

ビルド開始前にワークスペースを削除します。

「Windowsバッチコマンドの実行」に

コマンド : testrunner.bat -M -j -A Wether-soapui-project.xml

を入力します。

※testrunner.bat については公式ページを参照してください。

「JUnitテスト結果の集計」にて

テスト結果XML : **/TEST-TestSuite.xml

を入力します。

※TEST-<テストスイート名>.xml を指定します。

「保存」してJenkinsプロジェクトのページに戻ります。

※Jenkins自体の設定はこちらの記事のように設定しています。

Jenkinsジョブ実行

「ビルド実行」します。

ビルド結果を確認します。

テスト結果を確認します。

今日の天気は「曇」を期待していましたが、「曇時々雨」でした。

SoupUIでは以下のような感じです。

以上となります。お疲れさまでした。

タイトルとURLをコピーしました