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