My SoapUI Thoughts on Effective Testing in SoapUI

Compiling a JAR file

By now we have three copies of the Datasource base class, and I want to make use of it again one last time. In implementing a JSONDatasource class, we are very close to being able to support the last of our target file formats, the ubiquitous XML.

Prerequisites

  • A test case configured to test the users resource.

Goals

  • A compiled JAR file which implements support for CSV, Excel, JSON and XML data types.
  • An implementation which reads test data from an XML file.

To contain the test data for the albums resource, create an XML file called albums.xml in the SoapUI project directory with the following content.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<albumList numItems="3">
	<album num = "1">
		<id>39</id>
		<userId>3</userId>
		<title>Title goes here.</title>
	</album>
	<album num = "2">
		<id>59</id>
		<userId>5</userId>
		<title>Other title here.</title>
	</album>
</albumList>

You should also create a clone of one of the preceding test cases, changing the resourceName property on the new test case to albums and the PropertiesFile property to albums.xml. For now, it’s enough for the test case to contain test steps for the POST and GET methods. You can also create a Groovy test step as the first step in the test case, and we’ll populate it shortly with the call required to access our Datasource classes from within a compiled JAR file.

Here is what the XMLDatasource class code should look like.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@InheritConstructors
class XMLDatasource extends Datasource
{
	public boolean initialise()
	{
		try {
			final def rawData = new File( fileName )?.text,
				    XMLData = new XmlParser().parseText( rawData )

			def line = []
			XMLData.children()?.eachWithIndex { record, i ->
				if( i == 0 )
				{
					record.each { line += it.name() }
					testData.add( line )
				}					

				line = []
				record.each { line += it.text() }
				testData.add( line )
			}
		}
		catch( Exception e ) { testRunner.cancel( "$e" ) }
		true
	}
}

There are no specific import statements required for this class.

As you can see, the processing for XML is very close to that required for JSON data, reflecting the fact that both are hierarchical formats. This code uses the first of the XML records to collect the property names from the child members, and then collects the property values from all the child members, including the first.

The layout of the XMLDatasourceFull.groovy file is otherwise the same as that used for JSONDatasourceFull.groovy.

At this point, you should have created a test case using XMLDatasource to test the albums resource.

Compiling – updates to the source files

Now that our test case is running happily, it’s time to eliminate all these copies of the Datasource class, compile the XML processing into a JAR file and reference it from a Groovy test step. Let’s look at the changes to the source files required to support compilation into a JAR file.

It’s good practice when compiling files to specify a namespace. If you don’t, the classes will become part of the default namespace where they’ll be subject to potential collisions with other classes. I’ve decided to create a datadrivenTest namespace for these classes, specified via the package statement in the Datasource.groovy file as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/////////////////////////////////////////////////////////////////
//	Datasource driver - an opensource driver implementation.
//

package com.datadrivenTests

import com.eviware.soapui.impl.wsdl.teststeps.*
import com.eviware.soapui.impl.wsdl.teststeps.registry.PropertiesStepFactory
import com.eviware.soapui.model.testsuite.*

abstract class Datasource
{
	.
	. As above
	.
}

You might notice that the package statement comes before the import statements. Also, in this file and in the files for each of the derived classes, you only need to include the import statements relevant to that class.

The remaining source files should appear as follows.

CSVDatasource.groovy as shown here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
///////////////////////////////////////////////////////////////////
//	Datasource driver - an opensource driver implementation.
//	Read the properties provided in CSV format.
//

package com.datadrivenTests

import groovy.transform.InheritConstructors

@InheritConstructors
class CSVDatasource extends Datasource
{
	.
	. 
	.
}

XLSDatasource.groovy as shown here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//////////////////////////////////////
//	Datasource driver - an opensource driver implementation.
//	Uses the properties provided by the properties spreadsheet.
//

package com.datadrivenTests

import groovy.transform.InheritConstructors
import jxl.*

@InheritConstructors
class XLSDatasource extends Datasource
{
	.
	.
	.
}

JSONDatasource.groovy as shown here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
///////////////////////////////////////////////////////////////
//	Datasource driver - an opensource driver implementation.
//	Read the properties provided in JSON format.
//

package com.datadrivenTests

import groovy.transform.InheritConstructors
import groovy.json.JsonSlurper

@InheritConstructors
class JSONDatasource extends Datasource
{
	.
	. 
	.
}

XMLDatasource.groovy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
////////////////////////////////////////////////////////////////
//	Datasource driver - an opensource driver implementation.
//	Read the properties provided in XML format.
//

import groovy.transform.InheritConstructors

@InheritConstructors
class XMLDatasource extends Datasource
{
	.
	. As above
	.
}

Compiling - the build process

Ant, Maven, Gradle, Eclipse. The ecosystem around Java / Groovy source code compilation is pretty daunting to the casual user, particularly when all you want to do is squeeze out a JAR file from a couple of hundred measly lines of code. Happily for our purposes, it’s possible to get the job done without needing to get to grips with any of these heavyweight tools (although by all means use them if you’re comfortable with doing so).

All you actually need is the Java Development Kit from Oracle and the Groovy compiler. If you install the JDK first, the Groovyc installer will pick it up and set your environment variables appropriately.

To produce our JAR file, you can use the following build script on Windows 10 called build.bat. (If you’re working on Linux or OS/X, you’ll need to convert this batch file into a shell script.)

@echo off
set soapPath="c:\Program Files\SmartBear\SoapUI-5.2.1
set myPath=%soapPath%\bin\soapui-5.2.1.jar";
set myPath=%myPath%%soapPath%\lib\jxl-2.6.3.jar";
set myPath=%myPath%%soapPath%\lib\xmlpublic-2.4.0.jar"

groovyc -cp %myPath% -d datasourceClasses ^
	Datasource.groovy ^
	CSVDatasource.groovy ^
	XLSDatasource.groovy ^
	JSONDatasource.groovy ^
	XMLDatasource.groovy 

jar cvf datasource.jar -C datasourceClasses .

This file prepares a path to pass to the groovy compiler, including a couple of the SoapUI libraries related to Excel and XML handling. If you develop support for additional file types and want to include them in this build script, the only change required is to include the name of the groovy code file beneath XMLDatasource.groovy. Note that the groovyc command needs to be passed on one line, hence the ^ (caret) symbols at the end of the source file names. If you add more files, you’ll need to add a caret after XMLDatasource.groovy.

When invoked, the groovyc compiler will create class files for each of the groovy source files under the datasourceClasses directory. Lastly, the batch file calls the java compiler to convert the class files produced by groovyc into a single JAR file. Don’t forget the trailing . after the datasourceClasses directory name.

After you run this batch file you’ll find a datasource.jar file in the working directory. To deploy the JAR file, shut down SoapUI if it’s running, copy the file into SoapUI’s bin/ext directory, and restart SoapUI.

Invoking the compiled classes in a test case

To use the XMLDatasource classes in the compiled JAR file, you’ll need to create a Groovy test step in the albums test case with the following code.

1
2
3
4
5
6
7
import com.datadrivenTests.XMLDatasource
new XMLDatasource( log, testRunner, context ).with {
	if( initialise() ) {
		runTests()
		tearDown()
	}
}

The import statement refers to the class in the namespace we specified in the source code files.

That should be it! You should be able to use this approach to invoke any of the classes we’ve discussed: XMLDatasource, XLSDatasource, CSVDatasource, or JSONDatasource. The only change for each is the name of the class you want to import and instantiate based on the format of the test data you’re using.

Dynamic class selection

If you find yourself frequently invoking classes from within JAR files, you might be looking for a way in which your calling script can automatically select the name of the class based on some run-time condition. In our case, we have four classes, each relating to a specific type of data file. Groovy offers a way in which we can parse the Properties filename extension and dynamically select the appropriate class to handle the file.

Here’s how to do it:

1
2
3
4
5
6
7
8
9
10
11
def fileName    = testRunner.testCase.getPropertyValue( "PropertiesFile" ),
	fileExt     = fileName[ fileName.lastIndexOf( '.' ) + 1 .. -1 ]?.toUpperCase(),
	className   = "com.datadrivenTests.${fileExt}Datasource",
	dsClass     = Class.forName( className, true, this.class.getClassLoader() ),
	datasource  = dsClass?.newInstance( log, testRunner, context )
datasource?.with {
	if( initialise() ) {
		runTests()
		tearDown()
	}
}

This script makes use of Groovy’s dynamic string feature. The script establishes the name of the Properties file via the test case property and extracts the filename extension. This extension will be csv, json, xls or csv, and via a dynamic string ${fileExt} can be used to derive the correct class name. The script then retrieves the class from the jar file via Java’s Class.forName() method, and invokes it via newInstance() in ways we’ve used before.

With this technique, you’ll be able to use the same calling script in any of the data-driven test cases, without needing to modify it to accommodate different datafile types.

Working with databases

We’ve looked at populating our test data from local files. I also want to consider how a database can also provide a valuable alternative as a way of supporting data-driven testing.