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.

2 comments:

  1. The shared example works fine with OPen Source SOAP UI for SOAP Based Request.
    My project is calling REST based service using JSON request / response.
    Can you please help me to write a Groovy Script for Data Driven JSON Request. - REST Service.
    I am using SOAP UI Open Source Software.

    Regards



    ReplyDelete
    Replies
    1. I'm so sorry for the delayed response to this message-- there were a couple of things I wanted to take a look at first. I have to admit I have limited experience working with REST services. This is one way to adapt the data-driven model to REST/JSON, but may not necessarily be the easiest way.

      For the most part, getting the data-driven script model working with a REST service shouldn't involve too many changes. The process of looping through and reading data should be the same, and building assertions against JSON responses should be the same as with SOAP-- SoapUI will convert JSON to XML so you can use XPath assertions.

      I'm assuming your sending your JSON request data as part of your request body via POST. That's the one part that doesn't seem to be so easy to adapt. One thing that may work for you is adding some additional Groovy code to build your JSON request string, then setting that string as your request content. Here's some example code that does this:

      //Get your test request
      def tc = context.testCase
      def myRestRequestTestStep = tc.getTestStepAt(1) //index number may vary based on where your Rest test step is in your test case
      def myTestRequest = myRestRequestTestStep.getTestRequest()

      //Get properties and assign them to variables within the script
      def exJsonValue = tc.getPropertyValue("someProperty")

      //Build the string of JSON data to use in your request-- take care with escaping quotes you want to include in the JSON string
      def myJsonString = "{\"request\":{\"property1\":\"$exJsonValue\"}}"

      //Set the string as request content:
      myTestRequest.setRequestContent(myJsonString)

      Delete

Please be respectful of others (myself included!) when posting comments. Unfortunately, I may not be able to address (or even read) all comments immediately, and I reserve the right to remove comments periodically to keep clutter to a minimum ("clean" posts that aren't disrespectful or off-topic should stay on the site for at least 30 days to give others a chance to read them). If you're looking for a solution to a particular issue, you're free to post your question here, but you may have better luck posting your question on the main forum belonging to your tool's home site (links to these are available on the navigation bar on the right).