SoapUI: Writing Results to Excel

SoapUI's testrunner can be used to run test suites from the command line and provides an option to generate jUnit-style test reports.  These can be useful, of course, but they're not always easily readable.  In this post I'll cover how to collect test results and record them in an Excel document using the Apache POI API, downloadable here.  As a bonus, I'll also show some examples of Groovy closures in action to help streamline our script and avoid repetition.  Fair warning: this post's a bit long, but I really couldn't think of a good place to try to break it up.

I'll be building on the data-driven periodic table project using an Excel file as a test source; you can download this extended version of the project here.  Unzip the files (the project and source xls file) and import the project into SoapUI.  Modify the set up script for the ElementSpecificRequests test case to specify the location where you put the source data file (remember to double slashes in the path to escape them); likewise, you can change the location where you want to output your report file by modifying the test suite tear down script.  Run the suite, and it should create an Excel workbook containing test suite step results in the location specified in the suite tear down script.

For the sake of brevity I'll focus primarily on what's changed or new; you can read or review the details of the unchanged bits in the original post.  Setting up a project to use the API is also covered there: remember that to use the POI API library (or any third party library) within SoapUI, you need to drop its jar file into the bin/ext directory in your SoapUI program folder (a re-start of SoapUI may also be necessary).  This makes it possible to import and use the library's resources in your scripts.

Since we'll need to write to our report from both test cases, the code to set up the report is at the suite level.  Here are the first few lines of the set up script for the suite:

import org.apache.poi.hssf.usermodel.*

//Create report output workbook and worksheet
context.reportWorkbook = new HSSFWorkbook()
def reportSheet = context.reportWorkbook.createSheet("PeriodicTable-TestReport")

The import statement makes the members of the org.apache.poi.hssf.usermodel package available in our script-- components related to working with xls files.  A new HSSFWorkbook object is created and a sheet ("PeriodicTable-TestReport") is added to the workbook.  Note that the workbook is assigned to a context property (reportWorkbook) so it can be referenced again in the tear down script.

After the report workbook and worksheet are set up, we come to the first closure.  Closures are blocks of code that can be treated as pieces of data; i.e., they can be assigned to variables and properties, used as arguments to methods, etc.  While not the same as traditional Java functions, closures in practice behave very similarly.  In this case, a closure is used to encapsulate the code to write test results to the report worksheet to avoid re-writing the code in multiple places.  I'll review the closure in two parts:

//In the following closure tsResult represents a TestStepResult object
context.recordResults = {tsResult ->
   //Create new row in output workbook
   def newRow = reportSheet.createRow(reportSheet.getPhysicalNumberOfRows())
   def nameCell = newRow.createCell(0, HSSFCell.CELL_TYPE_STRING)
   def timeStampCell = newRow.createCell(1, HSSFCell.CELL_TYPE_STRING)
   def resultCell = newRow.createCell(3, HSSFCell.CELL_TYPE_STRING)
   def timeCell = newRow.createCell(2, HSSFCell.CELL_TYPE_NUMERIC)
   def assertFailCell = newRow.createCell(4, HSSFCell.CELL_TYPE_STRING)

   //Write test result data to corresponding cells in the newly created row
   def statusString = tsResult.getStatus().toString()
   nameCell.setCellValue(tsResult.testStep.testCase.name + "-" + tsResult.testStep.getName())
   resultCell.setCellValue(statusString)
   timeCell.setCellValue(tsResult.getTimeTaken())
   timeStampCell.setCellValue(new Date(tsResult.getTimeStamp()).toString())

The closure itself is demarcated by braces ({ and }), and the entire thing is assigned to the recordResults context property to facilitate re-use across test cases.  Note the odd-looking bit at the beginning of the closure (just after the opening brace): tsResult ->; this designates tsResult as the variable name used within the closure to represent the argument we'll eventually pass into it.  If this doesn't make sense just yet, it should be a little clearer once you see an example of calling the closure a little bit later.

The next non-comment line create a new row in the worksheet (newRow).  The createRow() method requires a zero-based index value to designate where in the sheet the row should be created (0 is the first row of the sheet, 1 is the second row, etc.).  Since we want to add in each new row after the existing rows, we use the getPhysicalNumberOfRows() method of the worksheet object to get the correct index we should use.  So, for example, if we have 6 rows already added in the worksheet, the physical row count (which is not zero-based-- it uses natural counting starting at 1) is 6.  The zero-based index of the last row is 5, hence the zero-based index we need to use for the next row is also 6.

The next few lines create the cells in the new row corresponding to test result data.  The createCell() method takes an index argument (zero-based again) specifying the column in the row where the cell should be created and a data type argument.

Finally we retrieve the test result data from the tsResult variable (which represents a TestStepResult object) and set cell values accordingly.  We're writing a test step name (created by combining the name of the step's parent test case with the name of the step separated by a dash), status, time taken, and time of execution.  There's a little bit of extra work involved in getting a string representation of the time of execution.  The time stamp is returned in milliseconds by the getTimeStamp() method.  That's used to create a new Date object, and that object's toString() method is called.

The last cell in the row, assertFailCell, should contain a list of any assertions (by name) that failed for a given test step.  The code to get that list of assertions makes up the second part of the closure:
 
   //Code to get the names of any failed assertions
   def failedAsserts = "" 
   if(statusString != "OK"){
      def assertList = tsResult.testStep.getAssertionList()
      assertList.each(){
         if(it.status.toString() != "VALID"){
            if(failedAsserts == ""){
               failedAsserts = it.name
            }else{
               failedAsserts += (", " + it.name)
            }
         }
      }
   }

   //Write the list of failed assertions to the appropriate cell
   assertFailCell.setCellValue(failedAsserts)
}

After defining a string where we can create our list of assertions (failedAsserts), there's an if block that contains code that's only executed if the value of statusString is not "OK"-- in other words, when the test step did not pass.  If the step didn't pass, we drill down into the step's assertions, getting the step result's parent test step, then using the step's getAssertionList() method to retrieve a list of assertions.

Once the list is assigned to the variable assertList, we call its each() method.  This is one of several special methods available to aggregate data structures (i.e., data structures like lists, maps, etc., capable of containing multiple values).  The each() method takes each member of the list and performs some action on it.  The action to perform is specified in a closure defined immediately after the method call-- note the opening brace after each() marking the beginning of the closure. Each member is passed as an argument to the closure and assigned to the variable it within the closure-- it is the variable name used when another variable name is not explicitly designated (as we did above with the tsResult variable).

In this case each assertion's status is checked.  If it's not equal to "VALID" (i.e., the assertion did not pass), the name of the assertion is retrieved and added to our comma-separated list of failed assertion names.  Once we've finished building the list, it's written to the assertFailCell cell in our report worksheet and the recordResults closure is finally ended with a closing brace.

I think it's important to point out that none of the code in the recordResults closure is actually executed here.  The closure is just defined the same way we might define a string or number variable or property for use elsewhere later on.

The last lines of the test suite set up script create a header row for our test report sheet:

//Write out report column headers to the report sheet
def headerRow = reportSheet.createRow(0);
def testNameCell = headerRow.createCell(0, HSSFCell.CELL_TYPE_STRING)
def timeStampCell = headerRow.createCell(1, HSSFCell.CELL_TYPE_STRING)
def testTimeCell = headerRow.createCell(2, HSSFCell.CELL_TYPE_STRING)
def testResultCell = headerRow.createCell(3, HSSFCell.CELL_TYPE_STRING)
def failedAssertions = headerRow.createCell(4, HSSFCell.CELL_TYPE_STRING)

//Populate header row cells with column names
testNameCell.setCellValue("Test Name")
testResultCell.setCellValue("Pass/Fail")
testTimeCell.setCellValue("Time Taken (ms)")
timeStampCell.setCellValue("Test Date/Time")
failedAssertions.setCellValue("Failed Assertions")

The suite tear down script is much shorter and should be relatively self-explanatory-- it writes the workbook data out to disk (modify the path string used to create the FileOutputStream object to change your output location and/or file name) and then closes the output stream used to write the data:

import org.apache.poi.hssf.usermodel.*

//Workbook exists in memory; write it out to a file here
def fout = new BufferedOutputStream(new FileOutputStream("C:\\Temp\\SoapUIRunReport.xls"))
context.reportWorkbook.write(fout)
fout.close()

Finally, we'll look at some code that actually uses the closure we defined above-- and you'll see just how useful closures can be.  Here's the code that's been added to the ReadNextLine Groovy script step in the ElementSpecificRequests test case:

def resultsList = testRunner.getResults()

context.recordResults(resultsList[-3])
context.recordResults(resultsList[-2])
context.recordResults(resultsList[-1])

This code gets the results from the three request test steps in the test case and writes their data (step name, time stamp, etc.) to the report workbook-- and it does all of this in four lines of code, thanks to the recordResults closure we created above.  There's a single line in the tear down script of the GetAtoms test case that does basically the same thing; however, the ElementSpecificRequests test case is a slightly more interesting example.

The first line calls the testRunner object's getResults() method to get a list of TestStepResult objects.  The last three lines take the last three members of that list and pass them as arguments to the recordResults closure.

You may be wondering what's up with the negative index numbers used to get the members of the list.  Because of the way we've implemented our data-driven testing, looping over the test case's test steps repeatedly, test results accumulate.  For example, the first time we reach the ReadNextLine script step, three step results have been recorded for the test case.  After looping through again, when we reach the ReadNextLine step we'll have seven step results for the test case (results for all four steps from the first time through, plus the first three steps for the second time through), and so on.  So each time through we only want to grab the last three available steps in the list of results, corresponding to the three request steps for the current loop.  Negative indexing allows us to do just that: using an index of -1 (resultsList[-1]) gets the last member of the list, an index of -2 gets the second to last member of the list, etc.

So once we have the last three members we can pass them to the closure.  In terms of syntax, you can see it looks just like calling a method-- we put our argument(s) to the closure inside parentheses after the name of the closure.  Scroll back up and take a look again at the recordResults closure to get a better idea of what's happening.  Remember we defined tsResult as the name of the variable holding our argument within the closure; when we pass resultsList[-1] into the closure, the last available TestStepResult object is assigned to that variable and the code in the closure is executed.

If you compare this project against the original data-driven xls project, you'll see that I've also made some changes to the code used to read data from the source file, re-organizing it into a closure so duplication of some code is avoided.  As this post is quite long enough already, I'll leave it to you to identify those changes on your own.

Automating SoapUI with Testrunner

In the SoapUI examples I've provided so far, test suites have been run through the UI.  At some point, however, you'll probably want to take your automation to the next step, running suites unattended and reviewing test results later.  We'll take a look at those topics over the next few posts, starting with SoapUI's testrunner.

Testrunner.bat typically resides in the bin sub-directory of SoapUI's program folder-- e.g., C:\Program Files (x86)\SmartBear\SoapUI-5.0.0\bin.  It allows you to run a SoapUI test suite from the command line with a variety of switches to set configuration options-- quite an extensive variety, in fact-- you can find the full list on the testrunner page on SoapUI's site.  Note that some of the switches listed here are only available in the Pro version of SoapUI.

Here's an example of a typical command sequence illustrating some of the more common switches you'll probably end up using:

"C:\Program Files (x86)\SmartBear\SoapUI-5.0.0\bin\testrunner.bat" -s"periodictableSoap TestSuite" -fC:\Temp\TestOutput -I -j -M C:\My Documents\MyPeriodicTableProject.xml

Going through each element of the sequence:

- "C:\Program Files (x86)\SmartBear\SoapUI-5.0.0\bin\testrunner.bat" : This is simply the path to testrunner.bat.  For those of you unfamiliar with using the command line, note the path is enclosed in quotations because it contains spaces.  Spaces normally separate elements of a command sequence; surrounding the path in quotations ensures the full path is treated as a single element.  Also, the full path is unnecessary if you've changed the working directory to the bin directory (i.e., you'd only have to specify "testrunner.bat"-- you can see this in the SoapUI TestRunner dialog below).

- -s"periodictableSoap TestSuite" : The -s switch precedes the name of the test suite within your project that you'd like to run-- as with the path to testrunner.bat, the name of the suite is enclosed in parentheses because it contains a space.

- -fC:\Temp\TestOutput : The -f switch precedes the path to the directory where test results will be saved.

- -I : This switch instructs testrunner to continue running even if an error is encountered.  Switches are case-sensitive (-i has a different meaning).

- -j : While SoapUI Pro supports a number of additional switches to generate different types of reports, the -j switch is available in the free version to generate JUnit-style xml reports.  These reports are saved to the directory specified with the -f switch above.

- -M : Separate from the JUnit-style report enabled by the -j switch, the -M switch saves a run log report to the results directory.

- C:\My Documents\MyPeriodicTableProject.xml : Finally, we specify the path to the project containing the suite to be run (no switch needed here).

Until you're comfortable with all of its switches and constructing a command sequence from scratch-- or just as a matter of preference-- you can also launch testrunner via the UI to let SoapUI generate a command sequence for you.  In the workspace tree, right-click on the name of the test suite for which you'd like to generate your command sequence (or the test project if you'd like to run multiple test suites in a given project) and select "Launch TestRunner" to bring up the configuration dialog.


Select your options from the various tabs in the dialog (the Basic and Reports tabs contain the more commonly used settings) and click the Launch button.  A command sequence corresponding to the selected configuration options is displayed near the top of the testrunner output (highlighted in the screenshot below).


Once you have this command line sequence it opens up a wide range of possibilities: you can incorporate it into your own batch files or Powershell scripts, integrate SoapUI test runs with an automation solution like Jenkins (incidentally, you can also use Jenkins to publish JUnit reports), schedule a run with Windows Task Scheduler, etc.

As mentioned above, the free version of SoapUI supports JUnit-style reports out of the box; however, it's possible to create some home-brewed reports using Groovy scripting.  We'll take a look at writing results to XLS files using the POI API in the next post.

Posts PDF and Downloads Page

Quite a while back (well over a year ago) I had a request from a reader to put my posts into a downloadable format.  I've finally gotten around to doing that; a PDF of almost all the posts published so far is now available on the new Downloads page (a link to the page is also available to the right).  I've re-ordered and grouped the posts in a way that I hope presents the information in a gradual, logical way.  Even so, it's difficult to translate blog posts to an offline format (obviously, they rely heavily on links).  I still hope some of you find it useful.  The sample projects referenced in many of the posts are also available for download on the same page.

XQuery in soapUI Part 2: Where and Order By Clauses

In the last post we took a look at the basic syntax of XQuery expressions in soapUI, including for and return clauses.  In this post we'll look at the optional where and order by clauses, which can add more complexity to expressions.

The XQuery where clause is very similar to the SQL where clause; it allows you to provide an additional filter on the base data set selected in your for clause.  In the last post we created an XQuery example using the Euro 2012 web service that retrieved data for each player returned in the AllPlayersNames test request.  Let's say we wanted to narrow down the list of players to include only members of the German national team.  We could adjust the query by adding a where clause just after the for clause:


In this case the where clause (where $x/m:sCountryName = 'Germany') whittles down the full set of tPlayerNames elements returned by the for clause, keeping only those that satisfy the condition of the where clause (in this case, those where the sCountryName child element equals "Germany").

Many of the same functions available in XPath expressions can also be used in XQuery expressions; the following is a valid where clause, for example:

where starts-with($x/m:sName/text(),'M')

The starts-with function takes two arguments and returns true if the first argument (in this case, the value of the sName element) begins with the second argument ("M").

In many cases, the choice to use a where clause may come down to preference; it's possible to achieve the same end result by using predicates with your for clause XPath expression.  This XPath expression in the for clause returns only German players (equivalent to the first where clause example):

for $x in //m:tPlayerNames[m:sCountryName='Germany']

Likewise, the following is equivalent to the second example, returning only those players with names starting with "M":

for $x in //m:tPlayerNames[starts-with(m:sName/text(),'M')]

For readability, however, you may find using where clauses preferable to the extended for clauses.

If a web service returns data in an arbitrary order (i.e., changing with each request), it presents a problem for XQuery assertions, which check for an exact match-- order absolutely matters.  For situations like these, an XQuery order by clause is necessary.  As you might expect, the order by clause is used to sort XQuery results by some node's value, ensuring results are always in the same order for evaluation by the assertion.  The order by clause is added before the return clause; here's an example of a complete XQuery expression including a for, where, order by, and return clause:


This example selects tPlayerNames elements from the response (the for clause), filters out only those elements with sName child element values that start with "M" (the where clause), orders the results by the value of the sName child element (the order by clause), and finally returns the values of the sName and sCountryName child elements for each tPlayerNames element, formatted within identifying tags (the return clause).

It's also possible to specify multiple values for the order by clause, establishing primary, secondary, etc. sort criteria.  For example, you can change the order by clause in the example above to the following to use sCountryName as the primary sort value and sName as the secondary sort value:

order by $x/m:sCountryName/text(), $x/m:sName/text()

The beginning of the results from this modified XQuery expression look like this:


As expected, results are sorted by country first, with players from the same country further sorted by name.

As usual when discussing XML-related topics, I highly recommend checking out W3Schools.com (available from the Useful Links section on the right) to find more information about using XQuery expressions.

XQuery in soapUI Part 1: For and Return Clauses

A few weeks ago I spent a few posts looking at XPath assertions in soapUI; in this post I'll take a look at XQuery assertions.  XQuery assertions can be used to retrieve data from XML using a SQL-like syntax.  The advantage of XQuery over XPath is that it can be used to check multiple node values with a single expression.  XPath expressions in soapUI must resolve to a single value (a count, a Boolean, a node, a node value, etc.); while you can use an XPath expression to validate some large chunk of static data, XQuery provides a little more flexibility, allowing you to pick and choose the nodes you'd like to have returned-- particularly useful in cases where there's a mix of static and dynamic data.

For an example, let's look at the Euro 2004 web service I introduced a few posts back and look at the AllPlayerNames request.  You can download a stripped down "test suite" consisting of a single test case here.  Import the project into soapUI and run the test request; it should return a list of participating players in the Euro 2012 tournament:


Let's say (strictly for the sake of this example) we're only interested in validating the iId, sName, and sCountryName values for each player, verifying they match their expected values.  Expand the Assertions panel for the test request, click the icon to add a new assertion, and select the XQuery Match assertion type (part of the Property Content group in the Add Assertion dialog) to bring up the assertion editor.

As with XPath assertions, you'll need to make some namespace declarations; click the Declare button at the top of the editor to let soapUI do this for you.  The XQuery expression below returns the iId, sName, and sCountryName values for all players; enter it beneath the namespace declarations as shown:


Click the "Select from current" button to evaluate the XQuery expression.  Here's a snippet of the results:


Let's break down the expression, starting with the for clause: for $x in //m:tPlayerNames.  If you're familiar with for-in or foreach-in loops in some programming languages, used to iterate through a collection and perform some action on each of its members, this is a bit similar.  The for clause here essentially translates to "for each member in the set of elements returned by //m:tPlayerNames..."  The clause also assigns those elements to the variable $x in subsequent lines of the expression.

The return clause does the heavy lifting, retrieving data associated with each element via XPath expressions.  For example, $x/m:iId/text() returns the value of the iId child element for each element. The text() function may be new for some-- it simply indicates that you want the actual inner value (i.e., the value between tags) of an element.  With soapUI's XPath expressions, the text() function isn't necessary (specifying an element is enough to indicate you want its value and not the node itself), it is required for XQuery expressions.

Tags are used to group and organize expression results.  Experimenting a little bit with the expression, you'll see a couple of general rules.  If the XPath expression in the for clause returns multiple nodes, the entire expression should be wrapped in tags (like the "playerList" tag above).  Likewise, you'll receive an error if you try to alter the example expression by removing the "player" tag around the return XPath expressions-- because there are multiple XPath expressions applied to each element, a tag is needed to separate each element's returned data (each id, name, and country set).  The "id", "name", and "country" tags are not in fact necessary, but they make the results more readable.

Within tags, brackets ( "{" and "}" ) are used to identify expression content that needs to be evaluated, as opposed to literal text, which can also be included between tags.  Here's a variation on the example expression that includes some literal text:


Here's what the result looks like:


As you can see, the literal characters within the tags (but outside the brackets) are left intact in the expression output.

Now that you're familiar with the basic syntax of XQuery expressions, we'll look at where and order by clauses in the next post.

REST Services and JSON in soapUI Test Suites

In the previous post I walked through adding a REST service to a project in soapUI; as a quick recap, here's a screenshot of where we left the project, with several REST requests:


As with SOAP services, once a REST service has been added to a project soapUI can automatically generate a test suite for the service.  Right-click on the service's node in the workspace tree and select "Generate TestSuite" from the context menu.  The Generate Test Suite dialog looks very similar to the corresponding dialog for SOAP services, allowing you to choose the resources for which you'd like to generate tests, whether you'd like all of the generated tests grouped into a single test case or assigned to their own test cases, etc.:


Let's leave the default options and click OK.  A test suite is generated with one test case for each of the REST service resources we defined previously.  By default, test requests use the parameter values that were set when the corresponding service request was added to the project.  However, these values can be changed in a test request's parameters table, and you can use them with property expansion.  Open the cardId test case and create a test case property called testCard and give it the value "chain-reaction".  Open the editor for its lone test request (Retrieve Individual Card Info) and use soapUI's property expansion syntax to specify the testCard property of the test case as the value of the test request's cardId parameter-- enter "${#TestCase#testCard}".  Click the Send button (the green arrow) for the test request.  Looking at the response, we can see that the data returned does indeed correspond to the card named Chain Reaction:


Remember that you can specify different parameter types in the parameter table, so you can use property expansion with any of them: URI parameters, resource path parameters, request body parameters, etc.

Let's move on to creating an assertion.  Up until now in my posts I've focused on web services that return response data as XML; this REST service gives us an opportunity to look at how soapUI handles JSON data.  SoapUI's solution for dealing with JSON data is really pretty straightforward-- it generates an XML representation of the same data set.  Consequently, all of the same XML-based content assertion types are available to use with JSON response data.  Note the tabs running along the left side of the panel showing the response data, and (if necessary) click on the one labelled "XML" to view the response in XML format.


Click on the Assertions button at the bottom of the Request Editor to open the Assertions panel, click the Add Assertion button, and add a new XQuery Match assertion.  The service returns data for multiple editions of the same card, so we'll use our assertion to confirm some expected values for the two editions of the Chain Reaction card.  Click the Declare button at the top of the assertion's configuration dialog to add a namespace declaration and add an XQuery expression to return data for the card's editions; the expression with its declaration should look something like this:


Click the "Select from current" button to capture the expected result of the expression, then save the assertion with the test request.  When dealing with XML-based assertions and JSON response data, note that even though the XML is auto-generated by soapUI, a namespace declaration and namespace prefixes are still needed.  Also be careful of extra tags in the XML (such as the "e" tags around each set of edition data) that do not exist in the original JSON but are inserted by soapUI to delineate some JSON data structures in the conversion.

Adding REST Services in soapUI

In this post I'll take a long overdue look at REST (REpresentational State Transfer) services in soapUI.  For anybody new to REST, I recommend this article covering the basics.  REST is not a web service protocol in and of itself; it's a web service architecture that can be implemented with any protocol, although it's frequently implemented with HTTP.  Occasionally, there's some confusion that arises from this relationship-- something to remember when thinking about your approach to testing a service.  For example, you may encounter a simple HTTP service labelled as a REST service that doesn't really take advantage of REST's architectural principles.  In these cases, it may be easier or more straightforward to implement test requests as basic HTTP requests in soapUI.

The last few releases of soapUI have also been very REST-centric, focusing on improved support and ease-of-use when dealing with REST services.  Going back just a few releases, creating REST services in a soapUI project was much more involved and complicated.  Consequently, if you intend to work with REST services I recommend updating to the most recent version if you haven't done so already.  This post reflects the workflow in soapUI 5.0.

For these examples, I'll be using the REST service at api.deckbrew.com.  The service provides information about cards from a popular collectible card game (Magic: The Gathering).  I apologize-- for most of you this subject matter will probably seem very dry, but it was surprisingly difficult finding a freely accessible web service that provided resources that could be used to create some meaningful examples.  This service is also limited in that it only leverages the HTTP GET method; an ideal REST service example would also utilize other HTTP methods (corresponding to different actions that could be taken on the service's resources).

When adding a SOAP service to a project, soapUI uses its WSDL document to determine the service's operations, their parameters, etc.  REST services have WADL (Web Application Description Language), which is roughly equivalent to SOAP's WSDL, but frequently a WADL document is not provided for a REST service (as is the case with our example service).  In these cases, soapUI allows you to enter service requests and parses the requests to identify the service's components.

From the soapUI File menu, select the option to create a new REST project.  You'll be prompted to enter a URI for the service:


To generate a request for a filtered card search, enter the following for the URI (as one continuous line):

https://api.deckbrew.com/mtg/cards?page=1&type=&subtype=&supertype=&name=&oracle=&set=&rarity=rare&color=red&multicolor=false&multiverseid=&format=&status=legal

In the workspace tree, soapUI generates a new project containing a node for the service's Cards resource, along with method and request child nodes corresponding to the URI we entered.  Click on the request to see the generated GET request:


All of the parameters entered as part of the request URI (including those for which no value was provided) are listed in the Request Editor.  You can add extra information about the parameters via the panel below the table; designating whether or not a parameter is required, for example.

The Level column determines the parameter's "scope" within the service-- a parameter can be associated with the RESOURCE level or the METHOD level.  Associating it with the RESOURCE level means the parameter is automatically "inherited" by any other method(s) created for its parent resource; it's added by default.  Associating it with the METHOD level means the parameter is only applicable to that method.  For this example, we'll just leave the parameters set to the default (RESOURCE).

A parameter's Style column indicates how the parameter is implemented-- the QUERY style corresponds to the standard "?parameter1=value1&parameter2=value2..." format you see as part of this GET request's URI.  Although POST requests are not supported by this example REST service, the QUERY type is also used when sending parameters in this format as part of the body (as opposed to the URI), as with POST requests.  For a POST request (and other methods allowing parameters in the message body), a checkbox is displayed that allows you to specify that parameters should be added to the request body instead of the URI.


To see an example of a different style, let's try adding a request to get the details for a specific card.  Right-click on the service in the tree and select "New Resource" from the context menu.  Another prompt appears-- note that once the host for the REST service has been established (via our previously created request), it's no longer necessary to enter the full URI when adding more resources.  The resource path by itself is sufficient.  Enter the following in the prompt:

/mtg/cards/about-face

You'll be asked if you want to add the new resource as a child of the existing Cards resource.  The parameters we added for the Cards resource aren't relevant to the single card request, so we're just going to add it as a completely separate resource.  Click No to the prompt.

There are no URI parameters for this request.  Instead, the last part of the resource path is the parameter.  We used "about-face" as the individual card id, but we can change the id to retrieve information for other cards.  This is where the TEMPLATE parameter style comes into play.  Open up the Request Editor dialog and add a new parameter called cardId.  Set its value to "about-face" and set the parameter's style to TEMPLATE.  You'll see the parameter appended to the resource path at the top of the dialog, enclosed in braces.  Edit the path to remove the hard-coded text "about-face", leaving the parameter to indicate the card id to retrieve:


After adding some additional resources and modifying some names to make them more descriptive, here's what our service looks like in our project:


In the next post, I'll look at using the REST service in a test suite.

Beginning soapUI Scripting: Javadocs 2

In my last post I started a discussion of javadocs, providing a high level overview. In this post I'll try to cover some of the keywords and concepts you may encounter when dealing with documentation for a class.

I have to admit I may have bitten off more than I can chew with this post, which accounts in part for the length of time it took me to publish it.  As I started looking at javadocs and javadoc keywords in more detail, I realized the post had to include explanations of a lot of basic Java concepts.  I hope I did an adequate job of explaining them, but I strongly encourage you to check out the official Java Tutorials to fill in the gaps (of which I'm sure there are a few).  I recommend the entire Learning the Java Language trail, but the particularly relevant sections include the sections on Classes and Objects and Interfaces and Inheritance.

Access Modifiers and Special Considerations for Groovy

Access modifiers-- private, package-private (which is the default when no modifier is specified), protected, and public-- specify the context in which a class and its members can be read and/or modified in code.  In the context of the basic usage of an API (when using it in a soapUI script, for example, as opposed to more advanced usage like making your own modifications to the source code), you're generally only going to deal with classes and members marked public.  The two most restrictive levels of access, private and package-private are reserved for those classes and members that represent "internal wiring"-- strictly intended for use within a class (private) or package (package-private), not intended for direct use by API consumers.  In fact, the Javadoc tool excludes classes and members with these access modifiers by default, in which case they don't even appear in documentation.  Members at the protected level are only slightly less restricted in that they're inherited by sub-classes of their enclosing class (even sub-classes that aren't in the same package)-- for somebody using the API, this comes into play when creating your own sub-class, something beyond the basic scripting covered here.

However, there's a very important caveat when working with Groovy.  When trying some scripting in soapUI with the HSSFWorkbook class, I was surprised to see that I could in fact access the _sheets field.  If you look at the documentation, this field is marked protected, meaning it should only be accessible from within the class, within other resources in the same package, or from within a sub-class-- a Groovy Script step doesn't fall into any of those categories.  After investigating, I found this interesting post indicating that Groovy doesn't always respect access modifiers (see item 6)-- that's potentially a bad thing; there are good reasons why an API developer might restrict access to a certain member of a class, particularly when it comes to modifying its value.  As the article says, just because Groovy allows you to do something doesn't mean you should do it; you may have to police yourself a bit-- be wary of accessing classes or members marked anything other than public.

Static and Final

The final modifier generally restricts a user's ability to modify a class or member, although it means slightly different things in different contexts.  Applied to a class, it means the class can not be extended-- you can not create a sub-class based on that class.  A final method can not be overridden in a sub-class; in other words, you can't change the way the method is implemented, writing new code for it in your sub-class.  The value of a field marked final can not be changed.  As with the more restrictive access modifiers discussed above, the final modifier at the class and method level isn't something you'll need to worry about too much with basic usage of the API; it's more relevant when you start dealing with sub-classes.  With fields, however, more often than not you'll see them marked final in javadocs, and the distinction is important even in the context of basic scripting.

The static modifier designates a class (as opposed to instance) member.  A non-static member is associated with an individual object-- an instance of a class.  I discussed the _sheets field of the HSSFWorkbook class above-- if you were to create two different HSSFWorkbook objects and add sheets to each, the _sheets field would be different for each object; each instance of HSSFWorkbook would have its own value for the field.  A static member, on the other hand, is associated with the class itself; when you access a class member, you access it through the name of the class, not the name of a variable representing an instance of the class:

log.info(myWorkbook._sheets) //_sheets is accessed for an HSSFWorkbook instance named myWorkbook-- can be done in Groovy
log.info(HSSFWorkbook.INITIAL_CAPACITY) //INITIAL_CAPACITY is accessed via the HSSFWorkbook class itself

It's common to use the static and final modifiers together with fields to create constants-- so common, in fact, that there's a convention in Java to use all caps in these fields' names (as with the INITIAL_CAPACITY field above).  Class constants often represent values that are frequently used; creating class constants for these values eliminates the need to repeatedly declare variables to hold them.  Java's Math class has the static final field PI, for example; similarly, the Color class has static fields to represent frequently used colors: RED, BLUE, etc.  Again, there's no need to create objects of class Math or Color to access these constants; they're called against the class names themselves (Math.PI or Color.RED, for example).

Methods, too, can be marked static.  Returning to Java's Math class as an example, aside from the standard methods it inherits from the object class, all of its methods are static-- the class is essentially a toolbox of mathematical operations.  As with static fields, static methods are accessed via the class instead of an instance.

Method Return and Parameter Types

Method definitions in javadocs specify return types (the data types of results from functions) and parameter type(s) (the types of the values passed into the functions).  For the most part, these should be pretty straightforward; for example, look at the WsdlTestCase class in the API documentation for soapUI.  In the Method Summary table, find the getTestStepByName() method; in the left-hand (Modifier and Type) column you can see that the return type is WsdlTestStep.  In the Method and Description column, you can see in the parentheses next to the method name that the method takes a single String argument called stepName-- note that stepName is a somewhat arbitrary parameter name; any variable name could have been used when the method was created (of course, stepName is a good choice because it describes the significance of the String argument).  From the perspective of somebody using the method, the parameter name itself is relatively unimportant; what is important is recognizing the method takes a single String argument and returns a WsdlTestStep object.

The void keyword is used for the return type when a method doesn't return a value.  The setName() function of the WsdlTestCase class, for example, changes a property of the test case without actually returning anything.  For functions that don't take any arguments, the parentheses next to the method name are simply left empty.

Some functions return multiple data items; for these, the "enclosing" data type is also indicated.  For example, the getTestStepList() method of the WsdlTestCase class specifies a return type of List<TestStep>, meaning it returns an object of class List containing zero (the list could be empty) or more TestStep objects (more about what that means below).  The getTestSteps() method returns an object of class Map; Maps consist of key-value pairs, so the return type specifies Map<String,TestStep>-- each key-value pair in the returned Map consists of a String (the key) and a TestStep object (the value).

In some cases, a method's parameter and/or return types are determined at runtime, in which case you'll see a slightly complicated-looking syntax. Look at the getTestStepsOfType() method, for example.  The return type is given as <T extends TestStep> List<T>.  There's no T class, of course.  The T is used as a placeholder for a data type to be determined later.  The <T extends TestStep> part of the return type tells us that there's a restriction on T-- it has to be a type that's derived from the TestStep interface (a class that implements the TestStep interface, for example, or a class that extends another class that implements the interface, etc.), and the rest (List<T>) tells us that the method returns a List of objects of that type.  So how is the type actually determined? If you look at the method description column, getTestStepsOfType(Class<T> stepType) tells us that type T is the type of the class passed in for the stepType argument-- so the argument determines the meaning of T in the return type definition as well.

Return Types and Polymorphism

Above, when discussing the getTestStepList() method, I wrote that it returns a list of TestStep objects.  If you were to look at the TestStep definition in the javadocs, however, you'll see that TestStep is actually not a class-- it's an interface.  In my last post I mentioned that you can't actually create an instance of an interface.  What's going on here?  This is actually an illustration of the object-oriented concept of polymorphism-- it's a source of great flexibility and power in Java, but for me, at least, it was a source of some confusion when I first started working with javadocs.  In a very simplified nutshell, polymorphism means an object can be many things at the same time. Because an object can extend another class and/or implement multiple interfaces, it shares their characteristics and can be used wherever a parent class or any implemented interfaces are called for.  So for example, if an interface is specified as a method parameter or return type (as with the getTestStepList() method), it means that the method will take or return an object of a class that implements the given interface.  The same principle applies to classes that are sub-classed: if a class is given as a parameter or return type, the method can actually take or return an instance of the class or any sub-class of that class.  For example, WsdlTestStep is the return type for the getTestStepAt() method.  If you look at the javadoc entry for WsdlTestStep, you'll see that it has three sub-classes: WsdlPropertiesTestStep, WsdlRunTestCaseTestStep, and WsdlTestStepWithProperties (which itself has its own sub-classes).  The specific class returned by the method, then, can be any of the sub-classes of WsdlTestStep (including the sub-classes of the WsdlTestStepWithProperties class).

The problem with polymorphism in the context of javadocs is that they may not indicate the specific class that's ultimately returned by a call to the method in your script.  Knowing the class of the returned object is important, of course, in order to know what methods and properties are available to that object.  If push comes to shove, you can identify an object's class by using the getClass() method-- this is a basic method inherited by all classes from Java's Object class:
def myTestStep = testCase.getTestStepAt(1)
log.info(myTestStep.getClass())
//Example output: class com.eviware.soapui.impl.wsdl.teststeps.WsdlGroovyScriptTestStep
//The return type specified in the javadoc for getTestStepAt() is WsdlTestStep
//The object returned is actually an instance of an indirect sub-class of the WsdlTestStep class

Beginning soapUI Scripting: Javadocs 1

I've written quite a few posts now on scripting in soapUI; hopefully they've provided enough useful information to get you started on your own projects.  However, you may come to a point where you're trying to do something for which you can't find a good example, or you've found an example but need some help understanding it.  Or perhaps you're just curious to see what else might be possible with a script you've already written.  In this post, I want to begin a series looking at javadoc documentation to help you explore the capabilities of Java and Groovy libraries on your own.

The javadoc format is standardized, generated directly from a resource's code and specially formatted comments and annotation within the code (Javadoc is the name of the tool used to do this).  Although it may be a little non-intuitive at first, its ubiquity across the Java world makes even a basic understanding extremely useful: if you can read javadoc documentation, you can learn more not just about soapUI but many of the other APIs we've discussed in previous posts: POI, Groovy, and, of course, the base Java library itself.

Let's take a look at the HSSFWorkbook class of the Apache POI API, which I think provides a fairly straightforward example-- for those of you unfamiliar with it, this API allows you to work with Microsoft document formats within Java; the HSSFWorkbook class corresponds to an Excel xls workbook.  Launch the documentation page via this link (I recommend opening the link in a new window or tab so you can read this post and see the example side by side): http://poi.apache.org/apidocs/index.html.  Near the top of the page or the top of the main frame on the right (depending on your current view), you'll see links/buttons for "Frames" or "No Frames" that you can use to switch between two available views; this post is written from the perspective of the "Frames" view.

The top left frame lists the packages of the POI API, the organizational units into which its resources are divided.  You can select an individual package to view its classes or "All Classes" to view all of the API's classes together.  Scroll through the packages in the upper left frame and select the org.apache.poi.hssf.usermodel package, then take a look at the lower left frame, which should now be populated with the package's contents.  In this case, they're divided into three different categories: interfaces (I'll explain more about these a little later), classes, and enums.  These aren't the only categories you may encounter-- another common category is exceptions, for example. For the sake of this post and to keep things simple, I'm going to think in terms of two primary groups: classes and interfaces (enums and exceptions are both special types of classes).  Note that interfaces and classes are distinguished from each other by the way they're displayed; interfaces are always displayed in italics.

Find the HSSFWorkbook class in the lower left frame and click on it-- the main frame on the right will update and display information specific to the class.

Just above the name of the class at the top of the frame is its enclosing package.  In the context of scripting, one reason this is important is for import statements.  Recall from my post about using the XmlHolder class in soapUI that an import statement was necessary in order to create an instance of the class:

import com.eviware.soapui.support.XmlHolder

def myHolder = new XmlHolder(messageExchange.getResponseContentAsXml())

The fully qualified name of the class (including its package) is needed for the import statement.

The fully qualified name is also displayed in the class's hierarchical structure, displayed just below the name-- a description of the classes from which it inherits properties and methods and the interfaces implemented by the class.  I'll try to explain these concepts briefly here, but I strongly recommend checking out the more detailed explanation here in Oracle's Java Tutorials.

One benefit of Java's object-oriented approach is the idea of inheritance-- when creating a new class you don't have to start from scratch; you can designate another class as its base class and the new class starts with some (or all) of its base class's methods and properties.  You can then override some of those inherited members (create a new implementation of a method, for example) or add new ones to make the new class more specialized and add additional functionality.  In fact, there are some classes called abstract classes that are designed specifically to be used as base classes.  You can't actually create an instance of an abstract class; you'll never work with an abstract class directly in code.  However, "concrete" classes (which can be instantiated) can inherit from it.

Interfaces are similar to abstract classes in some ways.  In simplified terms, interfaces primarily define a set of methods-- but they don't provide any actual implementation of those methods.  The methods in an interface have names and signatures-- describing what types of data the methods take as arguments and what types are returned-- but they don't have any code for the methods.  Where does the actual code come from?  A class can implement an interface; if it does, it is required to "fill in" the implementation of each of the methods defined for the interface.  Importantly, a class can inherit from only one class (although that class may itself inherit from another, which may inherit from another, etc.), but it can implement multiple interfaces.

Returning to our example of the HSSFWorkbook class, its javadoc page shows us that it's a sub-class of (i.e., it inherits from) the POIDocument class and it implements the Workbook interface.

Just below the hierarchy information is the class's declaration-- this is actually how the class is declared in its underlying Java code:

public final class HSSFWorkbook
extends POIDocument
implements Workbook

I'll discuss the meaning of some of the keywords like public and final in the next post.  This declaration also tells us some of the same information we saw in the hierarchy information: the extends keyword indicates the class's base class (POIDocument), and the implements keyword indicates interfaces implemented by the class (Workbook).

Just below this information is a description of the class (the description is a single line here, but this may be much longer and more detailed in some cases) followed by the meat of the javadoc entry: descriptions of its members, its properties and methods.  These are described in brief summary form at the top of the page, then in more detail farther down on the page.

Fields are the member variables of a class-- variables associated with the class itself (as opposed to variables that are declared within methods or code blocks-- a variable used as a counter in a for loop would not be a member variable, for example).  For fields that are not inherited, the summary section shows them in a table with modifying keywords and type (the data type/class of the field) on the left and a name and description on the right.  Inherited fields are simply listed with the class or interface from which they're inherited, linked to their detailed descriptions in those classes and interfaces.

Below the field descriptions are the class's constructors.  Constructors are special methods used to create new instances of a class when used in conjunction with the new keyword:

//An import statement is required to use the constructor
import org.apache.poi.hssf.usermodel.HSSFWorkbook

def myXLSWorkbook = new HSSFWorkbook()

Note constructors have the same name as the class.  Also note that with the HSSFWorkbook there are multiple constructors, demonstrating the concept of overloading-- its possible for a class to have multiple methods (this is not limited to constructors) with the same name as long as each method takes different sets of argument types.  For example, you can see that one constructor takes no arguments, while another takes an InputStream as an argument, and yet another takes an InputStream and a boolean (true or false) as its arguments.  This is permissible because the data types of the arguments are unique for each constructor; the interpreter or compiler can tell which particular constructor you're trying to call based on the types of the arguments passed into the constructor.

Below the constructors are the remaining methods of the class.  These are displayed in a table with modifying keywords and type (the data type returned by the method) on the left and the method name and parameter types (the data types the method takes) on the right.  As with fields, methods that are inherited from other classes or interfaces are listed separately, linked to their detailed descriptions in the corresponding class or interface.

That completes a quick overview of a typical javadoc page.  In the next post, I'll look at some of those keyword modifiers and what they mean in the context of scripting.

XPath in soapUI Part 4: XPath and Property Transfers

This post is the last in the series on using XPath in soapUI.  Up until this point we've been looking at XPath as it's used in XPath Match assertions, but it also features prominently in property transfers.  While there's not much by way of new syntax in this post (in fact, you'll probably find that expressions used in XPath assertions are generally more complex than the expression you'll use with property transfers), I think it's useful to see how XPath expressions can be used to designate source and target locations for property transfers.

This post uses the same sample project we've been using with previous XPath posts, built around a web service that returns information about holidays in several countries; you can download it here.  The test suite contains a test case called PropXferExample (the last test case in the suite) specifically created to illustrate using XPath in property transfers.  Let's take a quick look at the test case before we try to run it.

The test case begins with a straightforward test request:


This request asks the service for available US holidays, submitting "US" as the countryCode.  For this test case, we're going to take one of the holiday codes (corresponding to Valentine's Day) returned in the response to this test request and use it as a parameter in a subsequent test request.  Here's a look at an excerpt from the response, which we'll reference in the property transfer step:


The second step in the test case is a Property Transfer step, consisting of a single property transfer:


In the upper panel, we select our source location-- the location from which we'd like to copy our property value.  The AvailableHolidays test step (the first test request in the test case) is selected in the Source drop-down; in the Property drop-down, we indicate that we'd like to get our transfer value from the request's response.  Of course, we're not interested in the entire response, so we can use an XPath expression to specify precisely which part we would like to use; the XPath expression //Holidays[Name = "Valentine's Day"]/Key designates the value of the Key element for the Holidays element with the Name "Valentine's Day".  Just as with an XPath assertion, we can automatically declare namespaces to use with our XPath expression.  Note the button to auto-generate namespace declarations in the Property Transfer dialog looks different than the button in XPath assertion dialogs (simply labelled "ns:" here).  The XPath expression //Holidays[Name = "Valentine's Day"]/Key designates the value of the Key element for the Holidays element with the Name "Valentine's Day".

In the lower panel, we designate the destination for our transfer-- the location to which we'd like to copy the property value.  We could copy the property value to a test case or test suite property (useful if we intend to re-use it in multiple places throughout our script), but in this case we're only using it in the single request, so we can copy it directly via the transfer.  The request property of the GetHolidayDate test request step is selected as the target via the drop-downs, and the XPath expression (//ns1:holidayName) designates that we'd like to plug the copied property value into the holidayName element in the request.

To give some context to the expression, let's take a look at that request step now (again, don't run anything just yet):


This request sends a country, holiday (its key, not name, despite the name of the element), and year to retrieve the selected holiday's date.  Note the holidayName parameter is missing from the request (for now).

Run the test case and it should complete without errors.  The first test step retrieves the list of US holidays.  The second step finds the key corresponding to Valentine's Day in the response to step one and plugs it into the holidayName element in the final test request step.  Look at the second test request again after running the test case and you'll see that our missing parameter is now populated and we get a valid response:


Data-Driven Testing with XLS Example 2: Euro 2012 Web Service

This post covers a very basic sample project (really a single data-driven test case) to illustrate a data-driven testing technique using an Excel file as your test data source.  It's a simplified run-through of the same material covered here; this post is meant to provide an alternative working example.  There is some information in the original post not covered here (in particular, information about working with mixed data types); once you're familiar with the basics of working with the POI API covered in this post, I still recommend checking out the original post, particularly if you have to work with data types in the source sheet other than text.

You can download the sample project and its XLS file here in a single zipped file.  Unzip the files and import the project into soapUI.  You can place the XLS file wherever you'd like, although by default the script expects to find it in a directory called C:\Temp; you'll have to make a corresponding modification to the script if you'd like to put it somewhere else.

To use Excel sheets, you'll also have to extend soapUI's functionality using an external API; we'll be using the Apache POI API, which make it possible to access Microsoft documents (including Excel spreadsheets) from within Java.  First, download the Apache POI solution (the binary distribution) from here and unzip the downloaded file.  After unzipping, copy the main POI jar file (it should look something like "poi-versionnumber-releasedate.jar"; versionnumber and releasedate are placeholders for the actual version number and release date) into soapUI's special directory for external JARs: [SOAPUIProgramDirectory]/bin/ext-- of course, you should replace [SOAPUIProgramDirectory] with the path to the soapUI directory on your computer.  Once the POI JAR is in this directory, you should be able to access its members via import statements in soapUI scripts (note: soapUI may require a re-start before it recognizes the JAR file).

The web service contains information about the Euro 2012 soccer tournament.  We'll be using a data-driven testing approach to test the FullTeamInfo operation, which takes a country name as an input and returns details about that country's team, including players, coaches, etc.  Our source XLS file consists of a short list of country names in one column (which we'll use as a request parameter) and their corresponding coaches in a second column (which should be returned in the response).

If you've read my post on data-driven testing using a csv file, the general approach is the same; we're just using a different file type as our data source.  Our setup script contains code to open our source file and read in the first line; the country name and coach are assigned to properties we defined for the test case.  A test request is sent using the country name and an assertion verifies that the name of the coach returned in the response matches the name we read from the file.  A Groovy test step reads in the next line of the file (as long as the end hasn't been reached) and sends execution back to the test request step, where a new request is sent using the next row of data, etc.

Note that if you want to implement your own data-driven test case, you'll probably want to disable the Abort on Error option as I've done in this example project.  Otherwise, execution of the test case stops as soon as a failure occurs-- for a data-driven test case like this, we want to continue iterating over the loop to try all of our inputs even if one or more inputs fails.  The setting seems to be enabled by default for new test cases; to disable it, right-click on the test case, select Options from the context menu, and uncheck the corresponding checkbox:


Let's look at the various scripts in more detail, starting with the test case setup script:

import org.apache.poi.hssf.usermodel.*

context.rowCounter = 0
def srcBook = new HSSFWorkbook(new FileInputStream("C:\\Temp\\Euro2012.xls"))
def srcSheet = srcBook.getSheetAt(0)
def sourceRow = srcSheet.getRow(context.rowCounter)
testCase.setPropertyValue("Country",sourceRow.getCell(0).getStringCellValue())
testCase.setPropertyValue("Coach",sourceRow.getCell(1).getStringCellValue())
testCase.getTestStepAt(0).setName("FullTeamInfo - " + testCase.getPropertyValue("Country"))
context.srcWkSheet = srcSheet


Let's go through the script line by line; here's line 1:

import org.apache.poi.hssf.usermodel.*

This is the aforementioned import statement-- as I noted before, in order to read from an XLS file we have to extend soapUI's functionality; the import statement makes resources in the POI JAR file (placed in the bin/ext directory earlier) available in our script.

Next we set up a variable to keep track of our place (in terms of rows) in our source spreadsheet-- we'll set this as a property of the context variable so we can maintain its value across test steps and iterations:

context.rowCounter = 0

The next few lines create a workbook object representing our Excel source document and navigate down its structure using get methods, getting objects corresponding to the first worksheet of the workbook (using the workbook object's getSheetAt() method), then the current row in the sheet (using the sheet's getRow() method).  If you put the source xls file in a location other than C:\Temp, you'll want to modify the path in the first line below to point to the file's location on your computer (be sure to use double slashes for your path separators).

def srcBook = new HSSFWorkbook(new FileInputStream("C:\\Temp\\Euro2012.xls"))
def srcSheet = srcBook.getSheetAt(0)
def sourceRow = srcSheet.getRow(context.rowCounter)


Now that we have the first row in the worksheet (in variable sourceRow), we can read in the values of its cells using the getStringCellValue() method and assign them to the Country and Coach user-defined test case properties; we'll use these later in our test request and assertion.

testCase.setPropertyValue("Country",sourceRow.getCell(0).getStringCellValue())
testCase.setPropertyValue("Coach",sourceRow.getCell(1).getStringCellValue())


For readability, the next line re-names the test request step so we can see in the log which country is being used in each iteration.

testCase.getTestStepAt(0).setName("FullTeamInfo - " + testCase.getPropertyValue("Country"))

Finally, the source worksheet object is put into a context property.  This isn't absolutely necessary, but having it handy as a context property saves us a little bit of work in later steps getting another reference to the worksheet.

context.srcWkSheet = srcSheet

We're all set up now for our first test request, having read in the first line of data from our source spreadsheet.  Here's what the test request looks like, using property expansion to use the Country test case property:



FullTeamInfo test request using property expansion with the request parameter

And here's what our XPath assertion looks like to verify that the correct coach is returned in the response:



XPath assertion using property expansion to define the Expected Result

The remaining step in our test case (named ReadNextLine) is a Groovy Script step to prepare for the next iteration and control looping:

import org.apache.poi.hssf.usermodel.*

context.rowCounter++
if(context.rowCounter <= context.srcWkSheet.getLastRowNum()){
def sourceRow = context.srcWkSheet.getRow(context.rowCounter)
testRunner.testCase.setPropertyValue("Country",sourceRow.getCell(0).getStringCellValue())
testRunner.testCase.setPropertyValue("Coach",sourceRow.getCell(1).getStringCellValue())
testRunner.testCase.getTestStepAt(0).setName("FullTeamInfo - " + testRunner.testCase.getPropertyValue("Country"))
testRunner.gotoStep(0)
}


As with the setup script, we need resources in the POI JAR file, so the script starts with the same import statement.  Next we increment the context property we're using to keep track of our row count:

context.rowCounter++

Before we proceed, we have to check if the row value is valid for our source file-- i.e., we have to make sure we're not trying to read beyond the last row of the file.  The remaining lines of the script are wrapped inside an if block; the if statement compares the rowCounter value with the worksheet's last row value, returned by POI's getLastRowNum() method.

if(context.rowCounter <= context.srcWkSheet.getLastRowNum())

Consequently, the lines within the if block are executed only when the value of rowCounter is less than or equal to the total row count of our source worksheet.  The first few lines inside the block should look familiar: they're basically a repeat of the lines in the setup script responsible for getting a row in the worksheet, reading cell values from the row into test case properties, and renaming the request test step for readability:

def sourceRow = context.srcWkSheet.getRow(context.rowCounter)
testRunner.testCase.setPropertyValue("Country",sourceRow.getCell(0).getStringCellValue())
testRunner.testCase.setPropertyValue("Coach",sourceRow.getCell(1).getStringCellValue())
testRunner.testCase.getTestStepAt(0).setName("FullTeamInfo - " + testRunner.testCase.getPropertyValue("Country"))


Now that the next row's values have been read into their corresponding test case properties, the last line passes execution back to the test request step, the first test step in the test case (denoted by 0 using zero-based indexing).

testRunner.gotoStep(0)

The request is sent again and verified using the new values, after which the Groovy Script step runs again, getting the next row of values and sending execution back to the request step, etc.-- our loop is established.  Once we reach the last line of the source file, the if statement in the Groovy Script step will fail, skipping over the gotoStep method and allowing execution to pass on to the test case's teardown script, which consists of a single line to rename the test request step back to its "default" name.  Again, this isn't necessary, but it helps with keeping request inputs clear.

testCase.getTestStepAt(0).setName("FullTeamInfo")

Run the test suite and you should see it send the test request repeatedly, using the different countries specified in the source file as input parameters and verifying that the correct coach (also specified in the input file) is returned for each country.  I've "planted" a deliberate error in the source file, so you should see a failure occur for the test request for Germany:



Test Suite Log output

Data-Driven Testing Example 2: Holidays Web Service

In this post I'll walk through a sample soapUI project illustrating a data-driven testing technique that leverages test case properties and some light scripting.  This covers the same ground as my previous post on a simple data-driven testing technique, with a few minor differences; for example, this project uses XPath Match assertions where the original post used simple Contains assertions.

You can download the sample project and its accompanying CSV file (used as the source of our inputs and expected outputs) here in a single zipped file.  Download and unzip the files and import the project into soapUI.  You can place the CSV file wherever you'd like, although by default the script expects to find it in a directory called C:\Temp; you'll have to make a corresponding modification to the script if you'd like to put it somewhere else.

The service provides operations that return data related to holidays for the US and the UK; I've created test cases in the project for four of the operations, but it's the GetHolidayDate test case that we'll focus on in terms of data-driven testing.  GetHolidayDate takes a country, year, and holiday code (two of the other services show available country and holiday codes) and returns the date of the given holiday.

The holidays.csv file is our source file; each row of the file consists of a set of input parameters (country, holiday code, and year, in that order) along with our expected result-- the date corresponding to the input parameters.  Here are the contents of the file (just a sampling of all the available countries and holidays):

US,NEW_YEARS,2014,2014-01-01T00:00:00
GBEAW,BURNS_NIGHT,2014,2014-01-25T00:00:00
GBSCT,EMMELINE_PANKHURST,2010,2010-04-17T00:00:00
GBNIR,HOLOCAUST,2000,2000-01-27T00:00:00
US,EASTER,1980,1980-04-06T00:00:00
GBEAW,GUY_FAWKES,2008,2008-11-05T00:00:00
GBSCT,PALM_SUN,2013,2013-03-24T00:00:00
GBNIR,ST_PATRICKS_DAY,2005,2005-03-17T00:00:00
US,FLAG,2004,2004-06-14T00:00:00

We also have several test case properties defined:


Initially, these properties have no assigned values-- we'll fill those in with values from our source file as we iterate over the test case, doing the following:

1) Read a single line of the file, assigning the values in the line to corresponding test case properties.
2) Use property expansion to generate a request using the country, holiday code, and holiday year values we assigned to test case properties in step 1.
3) Use property expansion with an XPath assertion to confirm the response data returns the expected date, which was also assigned to a test case property in step 1.
4) Repeat steps 1 through 3 until the end of the file is reached.


The code to initialize our test data (opening the file for reading and processing in the first line of data) is placed in the test case setup script:

//Create a new BufferedReader object, using the context variable so it can be used between test components
context.sourceFile = new BufferedReader(new FileReader("C:\\Temp\\holidays.csv"))
//Read in the first line of the data file
def firstLine = context.sourceFile.readLine()
//Split the first line into a string array and assign the array members to various test case properties
def propData = firstLine.split(",")
testCase.setPropertyValue("holCountry",propData[0])
testCase.setPropertyValue("holCode",propData[1])
testCase.setPropertyValue("holYear",propData[2])
testCase.setPropertyValue("holDate",propData[3])
//Rename request test step for readability in the log; append code and year
testCase.getTestStepAt(0).setName("GetHolidayDate-" + propData[1] + "-" + propData[2])

The first (non-comment) line opens our file for reading via a BufferedReader object (if you put the csv file into a location other than C:\Temp, you need to modify this line accordingly).  The object is assigned to a property of the built-in context variable.  This allows us to share the object (and consequently the source file) and keep track of where we are in the file across multiple test steps and iterations.  Remember that the setup script is run prior to any test steps; it's not included in the steps that get looped over, which is what we want-- in particular, we don't want this first line to run more than once.

In the remaining lines of the setup script we read in the first line of our file, assigning it to a local variable firstLine (line 4).  In line 6, the line is split (using the comma to define our split points) and the split data items are assigned to an array called propData.  In lines 7 - 10 the members of the array are assigned to test case properties.  In the final line we're actually renaming the request test step by appending the holiday code and year; this is optional but makes the script log more readable.

Once the test case properties have been read into the script, property expansion is used to generate the test request:


The GetHolidayDate test request using test case properties

After reading in the first line of our data file, for example, the test request is sent using "US" as the countryCode, "NEW_YEARS" as the holidayName, and "2014" as the year.

A single XPath Match assertion is set up to validate our test request.  You can use property expansion with an XPath Match to specify the expected result; in this case, the holDate test case property is used:


Using property expansion with the XPath Match assertion for the GetHolidayDate test request

The next step in the test case is the ReadNextLine Groovy test step, responsible for managing iterations.  Here's the script contained in this test step:

/*Read in the next line of the file
  We can use the same object created in the Setup script because it
  was assigned to the context variable.*/
def nextLine = context.sourceFile.readLine()
/*If the end of the file hasn't been reached (nextLine does NOT equal null)
  split the line and assign new property values, rename test request steps,
  and go back to the first test request step*/
if(nextLine != null){
 def propData = nextLine.split(",")
 def curTestCase = testRunner.testCase
 curTestCase.setPropertyValue("holCountry",propData[0])
 curTestCase.setPropertyValue("holCode",propData[1])
 curTestCase.setPropertyValue("holYear",propData[2])
 curTestCase.setPropertyValue("holDate",propData[3])
 //Rename request test step for readability
 curTestCase.getTestStepAt(0).setName("GetHolidayDate-" + propData[1] + "-" + propData[2])
 testRunner.gotoStep(0)
}

The first non-comment line reads in the next line of our file, assigning it to the nextLine variable.  If we're not yet at the end of the file (if we were, nextLine would equal null), we basically go through the same code we saw before in the setup script: the data in the nextLine variable is split into an array, the values in the array are assigned to our test case properties, and the test request step is renamed to identify the current holiday code and year.  One difference here is that there's no built-in testCase variable as there is in the setup script, so we have to get a reference to the test case object via the built-in testRunner variable and assign it the variable curTestCase.  The final line uses the gotoStep() method of the testRunner variable to send execution back to the first test step in the test case-- the test request step.  After the new iteration of the test request step (with the newly assigned property values), the ReadNextLine step runs again, etc., until we finally do reach the end of the file.  Once this happens, the if statement fails and the gotoStep() method is bypassed.  Execution passes on to the short test case teardown script:

//Cleanup: rename test step to its original name and close the file reader
testCase.getTestStepAt(0).setName("GetHolidayDate")
context.sourceFile.close()

In this script we're just renaming the request step back to its default name (no identifying values appended) and the source file is closed.

Try running the test suite-- the resulting test suite log for the GetHolidayDate test case should look something like this (if necessary, click the TestSuite Log button at the bottom of the test case dialog to display the log):


We can see right away that something failed for the GetHolidayDate test request-- and because we were modifying the test step names as we went, we can see it occurred for Emmeline Pankhurst Day, the third row in the source file.  If you want to see more information about the failure, you can simply click on the error to launch the Message Viewer.  Here you can see the request and response XML (including the values resulting from property expansions) to get a better idea of where the problem may be.  In this case, our source file indicated that the expected date for the holiday was 2010-04-17, but the service returned 2010-07-14 (this was in fact an intentional error in the source file).
That illustrates one way to perform data looping with the soapUI free version, but by no means the only way; keep in mind that soapUI Pro includes additional tools to facilitate data driven testing.  If data driven testing is something you intend to use heavily or your particular case requires a complex implementation, the additional cost of the paid version of soapUI may be worth the savings in time and effort invested in maintaining scripts.