soapUI: Data-Driven Testing with Excel (xls)

Note: If you experience any issues with webservicex.net or the periodic table service used in this post, or would just like to see another example, I've provided an alternative post covering some of the same material with a different service here. There is some material in this post, however, that's not covered in the alternative post (in particular, information about the DataFormatter class and using different data types)-- based on your own needs/situation, you may still want to come back here once you've seen a working example.

In response to a comment on a previous post about data-driven testing in soapUI, I'm going to try to illustrate a solution that works (with some limitations) for Excel data sources.

Generally speaking, it's easiest to work with data as text.  Property-related "set" and "get" methods take and return strings, so you end up having to convert numbers to text anyway-- and that's where things can get a little messy.  While the numbers 24.0 and 24 are equal, the strings "24.0" and "24" are not.  Of course, formatting all your numbers as text may be defeating the purpose of using Excel in the first place.

The basic idea behind the Excel test suite is the same as with the CSV-driven suite: each row of the source file consists of inputs with some expected response data; these are copied to test case properties and property expansion is used to plug the inputs into test requests and the response data into assertions.  A Groovy script step controls reading in the next row of data and repeating the test case until the end of the file is reached.  You can see the post covering a CSV-driven suite here for a more detailed explanation of the basic structure.  This post will only look at the script modified to use Excel as a data source (everything else is basically the same).

The suite and xls source file can be downloaded here in a zip file-- unzip the contents and import the project xml file into soapUI (File - Import Project).  By default, the script expects the XLS file to be located in a directory called C:\PerTableData; if you want to put it anywhere else you'll have to modify a single line in the script.

To use Excel sheets, you'll also have to extend soapUI's functionality using external APIs; in this case, 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.  Once the POI JAR is in this directory, you should be able to access its members via import statements in soapUI scripts.

Before we get to the script itself, lets take a look at our Excel source file to see a couple of the potential problems it presents.  Note that one cell in the second column (the atomic number for oxygen) is actually represented with a SUM formula; otherwise, the numbers are integers.  The last column (representing boiling points) could be even more problematic.  For one thing, the first row contains a string while the rest of the rows are numeric.  Even among the numeric rows, the types of numbers vary: the boiling points for carbon and gold are integers, for example, while the boiling points for helium and oxygen are floats.

Let's break down the Setup script for the ElementSpecificRequests TestCase (containing the steps to be repeated based on our source data).

The first line is an import statement importing the contents of the POI package containing the classes we need to work with Excel files-- again, placing the POI jar containing this package into the soapUI bin/ext directory as described above makes it possible to use this import statement.

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

The next few lines set up some POI objects we'll need for our script: an HSSFDataFormatter to handle the formatting of our numbers when they're converted to text (unnecessary if you're only using text data), an HSSFWorkbook object to open and read our source XLS file-- this is the line you'd need to alter if you changed the location of the source file from the default, an HSSFFormulaEvaluator to handle cells containing formulas (this object is not required if your source spreadsheet does not contain any formulas), and an HSSFSheet corresponding to the first sheet of our source workbook.  The script assumes the source data is contained on the first sheet-- if that's not the case for your own workbook, change the zero-based index of the sheet passed to the getSheetAt() function (first sheet is index 0, second sheet is index 1, etc.); alternatively, there's a getSheet() function where you can get a sheet by name-- e.g., sourceSheet = srcBook.getSheet("mydatasheet").

//Create data formatter
dFormatter = new HSSFDataFormatter()

//Create a new workbook using POI API
srcBook = new HSSFWorkbook(new FileInputStream(new File("C:\\PerTableData\\TestData-Mix.xls")))

//Create formula evaluator to handle formula cells
fEval = new HSSFFormulaEvaluator(srcBook)

//Get first sheet of the workbook (assumes data is on first sheet)
sourceSheet = srcBook.getSheetAt(0)

Next, we create a rowCounter variable in our test case's context, setting it to 0-- we put it in test case context to keep the variable between steps and iterations of the test case.

//Sets row counter to 0 (first row)-- if your sheet has headers, you can set this to 1
context.rowCounter = 0

We use the POI HSSFSheet class's getRow() function to read in the first (zero-based index 0) row of our spreadsheet. If your source spreadsheet contains one or more header rows, you may need to adjust this number accordingly.

//Read in the contents of the first row
sourceRow = sourceSheet.getRow(0)

The heart of reading in the source data is in the next few lines, where we take the row object we created in the line above and read in the data from each cell, assigning it to one of our test case properties via the setPropertyValue() method.  Note the use of the HSSFDataFormatter object (dFormatter) we created and its formatCellValue() method.  In the POI API, if you attempt to simply read in a numeric cell's value and then convert it to a string via the toString() method, it'll read in the number as a float before converting it to a string-- so if your cell contains the number 25, for example, it ends up getting formatted as "25.0" as a string.  The formatCellValue() method of the HSSFDataFormatter class handles this particular scenario, formatting the number 25 as "25".

//Step through cells in the row and populate property values-- note the extra work for numbers
elNameCell = sourceRow.getCell(0)
testCase.setPropertyValue("ElName",dFormatter.formatCellValue(elNameCell,fEval))

atNumCell = sourceRow.getCell(1)
testCase.setPropertyValue("AtNum",dFormatter.formatCellValue(atNumCell,fEval))

symbolCell = sourceRow.getCell(2)
testCase.setPropertyValue("Symbol",dFormatter.formatCellValue(symbolCell,fEval))

atWtCell = sourceRow.getCell(3)
testCase.setPropertyValue("AtWeight",dFormatter.formatCellValue(atWtCell,fEval))

boilCell = sourceRow.getCell(4)
testCase.setPropertyValue("BoilPoint",dFormatter.formatCellValue(boilCell,fEval))

One more note about the formatCellValue() function: the second argument (fEval, representing our HSSFFormulaEvaluator object) is only necessary if you're working with a source spreadsheet containing formulas-- otherwise, the function only needs one argument: for example, dFormatter.formatCellValue(elNameCell).  If you're working exclusively with text cells, even the dFormatter object and formatCellValue() method are unnecessary-- you can use the cell class's toString() method directly on each cell-- e.g., testCase.setPropertyValue("BoilPoint",boilCell.toString()).

The next few lines are actually optional-- they rename test steps for readability in the test suite log, appending the names of elements so you can keep track of which row is being processed.

testCase.getTestStepAt(0).setName("GetAtomicNumber-" + testCase.getPropertyValue("AtNum"))
testCase.getTestStepAt(1).setName("GetAtomicWeight-" + testCase.getPropertyValue("AtWeight"))
testCase.getTestStepAt(2).setName("GetElementySymbol-" + testCase.getPropertyValue("Symbol"))

Finally, we copy our sourceSheet to a variable in test case context to save ourselves the trouble of having to re-create it later on.

context.srcWkSheet = sourceSheet

Now let's look at the ReadNextLine Groovy script step, run after our test requests are sent.

The first few lines are nearly identical to the Setup script described above, importing the contents of our POI package and creating a few needed objects.  Since we put the worksheet object created in the Setup script into context, we don't have to explicitly create another workbook object-- we can use the worksheet object's getWorkbook() method to get an object corresponding to our workbook.

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

cellDataFormatter = new HSSFDataFormatter()

//Create formula evaluator
fEval = new HSSFFormulaEvaluator(context.srcWkSheet.getWorkbook())

The next line increments our counter variable.  This is the same variable that was created in our Setup script; again, we can re-use it and keep track of its value between test steps because it's in test case context.

//Increment the rowcounter then read in the next row of items
context.rowCounter++;

The code to read in row data is wrapped in an if statement that checks to see if the end of the file has been reached (using the getLastRowNum() method of the POI HSSFSheet class). The last line of the if block goes back to the first test request step, running through the test case's test requests again with the newly copied property values.  Otherwise, this code is nearly identical to the corresponding code in the Setup script.

if(context.rowCounter<=context.srcWkSheet.getLastRowNum()){
 curTC = testRunner.testCase
 sourceRow = context.srcWkSheet.getRow(context.rowCounter)//Get a spreadsheet row
 
 //Step through cells in the row and populate property data 
 elNameCell = sourceRow.getCell(0)
 curTC.setPropertyValue("ElName",cellDataFormatter.formatCellValue(elNameCell,fEval))

 atNumCell = sourceRow.getCell(1)
 curTC.setPropertyValue("AtNum",cellDataFormatter.formatCellValue(atNumCell,fEval))

 symbolCell = sourceRow.getCell(2)
 curTC.setPropertyValue("Symbol",cellDataFormatter.formatCellValue(symbolCell,fEval))

 atWtCell = sourceRow.getCell(3)
 curTC.setPropertyValue("AtWeight",cellDataFormatter.formatCellValue(atWtCell,fEval))

 boilCell = sourceRow.getCell(4)
 curTC.setPropertyValue("BoilPoint",cellDataFormatter.formatCellValue(boilCell,fEval))

 //Rename test cases for readability in the TestSuite log
 curTC.getTestStepAt(0).setName("GetAtomicNumber-" + curTC.getPropertyValue("AtNum"))
 curTC.getTestStepAt(1).setName("GetAtomicWeight-" + curTC.getPropertyValue("AtWeight"))
 curTC.getTestStepAt(2).setName("GetElementSymbol-" + curTC.getPropertyValue("Symbol"))

 //Go back to first test request with newly copied properties
 testRunner.gotoStep(0)
}

The suite structure and scripts illustrated here should work for most typical scenarios with an Excel input file; however, you may need to modify some of the code depending on your particular test data and its formatting standards. The usual disclaimers also apply: I'm not a programmer by profession, so I'm sure there's plenty of room for improvement; I encourage you to explore other techniques and the POI API more thoroughly on your own (documentation for the POI API can be found here).

10 comments:

  1. Thanks for sharing the step by step instructions as well as sample. I was able to convert my test script to excel data driven script very quickly using these instructions.

    ReplyDelete
  2. Much thanks for detailed step by step instructions on data driven testing with excel. I was able to implement Soap automation with your blog. I read all your SoapUI posts and was very impressive. Please do more posts on SoapUI concepts. Myself and all Soap automation guys will get benefited.

    ReplyDelete
  3. Thanks a lot for the posting as well as the attached source code.

    ReplyDelete
  4. Thanks a ton, Very useful and detailed examples :)

    ReplyDelete
  5. I am getting the following error can u please help me

    org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: Script2.groovy: 2: unable to resolve class HSSFDataFormatter @ line 2, column 14. dFormatter = new HSSFDataFormatter() ^ org.codehaus.groovy.syntax.SyntaxException: unable to resolve class HSSFDataFormatter @ line 2, column 14. at org.codehaus.groovy.ast.ClassCodeVisitorSupport.addError(ClassCodeVisitorSupport.java:146) at org.codehaus.groovy.control.ResolveVisitor.resolveOrFail(ResolveVisitor.java:222) at org.codehaus.groovy.control.ResolveVisitor.resolveOrFail(ResolveVisitor.java:232) at org.codehaus.groovy.control.ResolveVisitor.transformConstructorCallExpression(ResolveVisitor.java:969) at org.codehaus.groovy.control.ResolveVisitor.transform(ResolveVisitor.java:646) at org.codehaus.groovy.control.ResolveVisitor.transformBinaryExpression(ResolveVisitor.java:937) at org.codehaus.groovy.control.ResolveVisitor.transform(ResolveVisitor.java:640) at org.codehaus.groovy.ast.ClassCodeExpressionTransformer.visitExpressionStatement(ClassCodeExpressionTransformer.java:139) at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:40) at org.codehaus.groovy.ast.CodeVisitorSupport.visitBlockStatement(CodeVisitorSupport.java:35) at

    ReplyDelete
    Replies
    1. This looks like an issue with the import statement. Did you include that at the top of the script? Or if the import statement looks right, is the poi jar file in the bin/ext directory?

      Delete
    2. Great Stuff Mate

      Delete
    3. Hi Sanjana,
      I got the same error as you have pasted and it seems to be be because an 'xlsx' file was used instead of 'xls' file. You can either use an xls file as input and above code in the blog should work fine. Else you can refer to below link and follow the instructions->
      http://stackoverflow.com/questions/9621131/error-while-reading-excel-sheet-using-java

      Regards,
      Deepak.

      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).