Multithreaded load testing with soapUI
Introduction
To realistically load test our vehicle data webservice we wanted to replay actual requests made by customers to our test environment. So from our production log files we extracted the request type, the product codes and the vehicle registration marque into a CSV file. This file was then used as a data source for our load test.
A simple load test in soapUI can be created with minimal effort. To replay requests in a sequence in a multi-threaded load test requires a synchronized data feed. This can be achieved by using Groovy code steps in the test case. The code samples in this article will show you how to do this in a thread safe manner.
The screenshot shows the layout of the project, the Groovy code test steps are indicated by blue stars.
Synchronized Data Feed
The first step initialise LoadTest contains the class definition for the data feed and safe instantiation of the data feed object.
import groovy.transform.Synchronized class SyncDataFeed { static def BufferedReader dataReader=null static def int vrmCount=0 static def PrintWriter report=null static def int maxReads=0 static def reportEnabled=false SyncDataFeed(String filename, String reportFile,int count, boolean enableReport){ maxReads=count dataReader = new BufferedReader(new FileReader(filename)) reportEnabled=enableReport if(reportEnabled) { report= new PrintWriter(reportFile) report.println("Setup Data Coverage Test using input file "+filename) report.flush() } } @Synchronized def getNextLine(){ vrmCount++ if(vrmCount>maxReads) { return null } else { def inputText= dataReader.readLine() Date now=new Date() if(reportEnabled) { report.println(now.toString()+" "+Thread.currentThread().name+" "+inputText) report.flush() } return inputText } } }
if(context.LoadTestContext!=null) { synchronized( context.LoadTestContext ) { if(context.LoadTestContext['syncFeed']==null) { context.LoadTestContext['syncFeed']=new SyncDataFeed("/LoadTest/data/load_test_enquiries.csv","/soapUILogs/report.log",2000000,true) } } } else { log.info("do nothing for single threaded TestCase run") }
Reading the data and populating the SOAP request
The next step readInputFile will use the syncFeed object to read from the input file
def inputText=null if(context.LoadTestContext!=null) { inputText=context.LoadTestContext['syncFeed'].nextLine } else { //single threaded inputText=context["dataFeed"].nextLine }
inputText contains the a single line of comma separated values to be used in the request. Using a string tokenizer these are put in separate variables.
def tok= new java.util.StringTokenizer(inputText.trim(),",") def enquiryType=tok.nextToken() def customerCode=tok.nextToken() def vrmText=tok.nextToken() def primaryProduct=tok.nextToken()
These are then loaded into test case properties which can be used as parameter values in a SOAP request
e.g.
testRunner.testCase.setPropertyValue("primaryProduct",primaryProduct)
is then used in the XML body of a SOAP request
<Code>${#TestCase#primaryProduct}</Code>
Selecting which SOAP request to make is a simple test then a goto
if(enquiryType.equals("core")) { testRunner.gotoStepByName("prepareCore") }
From the prepareCore step we move onto making the CoreRequest. Within the request step we add some basic assertions to check it responded correctly within a certain time period. The coreDone step simply loops back to the readInputFile step. The test continues with the next line from the input file which populates the next request in the sequence. In a load test this will all be done in parallel by multiple threads.
Running the Load Test
We can run TestCase1 in a single thread to prove it works but what we really want to do is run multi threaded LoadTest1. The soapUI LoadTest window gives many options such as how many threads to use, what strategy and for how long. When the test is complete it gives stats on what happened. In practice we ramped up the loading to see what volume of requests the service could cope with and still return responses within an acceptable time.
Conclusion
To develop a thread safe load test create your own data feed class with synchronized methods that read from a source file or database. Use the context.LoadTestContext object to store the instance of this data feed and to determine if you are in "load test" mode. Populate your soap requests with values taken from the data feed using test case properties. Then run the load test multiple times increasing the number of threads until you see your system performance drop below an acceptable level.
This approach can also be used with JSON based Restful services. The same Groovy code can be used and the test case properties can be used as parameters within the Restful requests.