Gatling and Scala

Gatling and the Scala that it uses has quite a bit in common with Java – in this case it is the virtual machine. The Scala code is compiled to byte code and is run on the same JVM that your Java programs run on. Yet the scala script may look a bit odd even if you are a Java programmer. There are quite a few elements that should be fairly similar once you are familiar with the syntax.

Include files

import io.gatling.core.Predef._

The format is slightly different but this is almost identical to a Java import.

import java.io.*

Scala uses a underscore for the wild character and does not require a semicolon to finish the line.

Variables

var username = “chris”;
val password = “password”;

Scala doesn’t actually force you to use actual data types but rather the variable types can be determined by the types of data that is assigned to them. The only two key words you need to remember are var for variables and val for constants. Constants are equivalent to Java variables that are of type “final”.

Methods or Functions

// our own sample function
def myimporttext2(inputval : String, requestedIQ : Integer ) : Int = {
println(inputval)
return 125
}

Although my recorded script doesn’t explicitly have any functions defined it is possible to create your own functions which can be used to perform common tasks.

This Scala example is pretty non-sensical but you can see that it is possible to create a function with multiple inputs. It is also possible to return a value from the function which could be perhaps used in a calculation. It may not be obvious but it is trivial to do such functions with all strings some of the problems may occur when you try and use other data types. This example is returning the primitive Int because it is not possible to return the data type Integer.

Not all functions look alike. It is also possible to define a small chunk of code as seen below.

More methods or functions

val accountNotFoundCheck = regex("<returnCode>1</returnCode>").exists
val returnCodeZeroCheck = regex("<returnCode>0</returnCode>").exists
val bodyFetch = bodyString.saveAs("BODY")
 
This type of function can be used when performing a check after making a rest call.  The last of these was not to do a test but to save the body of a HTTP GET but to do so in an easy way.  Below is an example of actually using these functions.
 
 exec (
 http("update account")
 .post(maintainacctservice)
 //.body(RawFileBody("xsys\\2_XSYS-UpdateAccount.xml"))
 .body(StringBody(updateaccountdataxml))
 .check(returnCodeZeroCheck)
 .check(status.is(200))
 .check(bodyFetch)
 ) 

It might not be obvious in the example code from the recorder but it is possible to do multiple checks after performing a call. Simply add as many checks as is necessary to extract all the information or for verifying correctness.

More variables

Just like java it is possible to have variables and to have constants. Yet, test scripts would either be really long or impossible to manage if you had to create every variable individually as described above. It is possible to abstract out which values need to be changed and which may need to be grouped together. One fairly obvious example might be a users login credentials. We could run all of our tests with a single user but if the test is actually retrieving information from the database our tests might not be accurate as the database server may be caching that particular users information as it is requested so often.

In Gatling will simply group these values into a comma separated file. The file and a bit of syntactic sugar and voila we have converted those repetitive values into variables that our Gatling script can use.

File sample

first,last,id
paul,johnson,98
samual,smith,102
jack,jones,125

Each comma separated value from the first line of the file will be used as a variable. Gatling can use these csv files in several different ways. There are four different methods but in practice there are only really three.

TypeDescription
QueueRead a value but generate an error if end of file is reached
RandomPick values from list at random
CircularRead items from list but go back to top if you reach end of file
ShuffleShuffle the entries and then treat list like a queue

Using one of these strategies is actually pretty easy.

val scn = scenario("RecordedSimulation")
 .feed(csv("users.csv").circular)
 .exec(http("search asrock")
 .get("/computers?f=asrock")
 .headers(headers_4)
 .check(status.is(200)) 
)

Simply add the csv file in at the top of the run and with each iteration it will take a new entry from the data file. In my example, I am not actually using these values but if we wanted we could change the strategy to random and then have lists of computers that will be found or that will not be found. This would give a certain amount of realism to the test and would prevent the database server from caching values to improve the response times in our test.

All of this is accurate but it is difficult to get a good overview of what the original script does. This is a bit sad considering that the original script is quite short. It is possible to convert this script which is essentially “flow of consciousness” into something that is a bit more organized. I have take recorded example and organized it by task. Once this is done, below, you can see this is much much easer to read and modify if necessary.

 import scala.concurrent.duration._
 
 
 import io.gatling.core.Predef._
 import io.gatling.http.Predef._
 import io.gatling.jdbc.Predef._
 
 
 class ModifyRecordedSimulation extends Simulation {
 
 
 var httpProtocol = http
 .baseUrl("http://computer-database.gatling.io")
 .inferHtmlResources(BlackList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.woff2""", """.*\.(t|o)tf""", """.*\.png""", """.*detectportal\.firefox\.com.*"""), WhiteList())
 .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
 .acceptEncodingHeader("gzip, deflate")
 .acceptLanguageHeader("de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7")
 .userAgentHeader("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36")
  
 httpProtocol = httpProtocol.proxy(Proxy("localhost", 31288).httpsPort(31288))
 
 val headers_4 = Map(
 "Proxy-Connection" -> "keep-alive",
 "Upgrade-Insecure-Requests" -> "1")
  
 object TestHelper {
  
 var searchFailing =
 exec(http("search asrock")
 .get("/computers?f=asrock")
 .headers(headers_4)
 .check(status.is(200))
 )
  
 var searchExisting =
 exec(http("search mac")
 .get("/computers?f=mac")
 .headers(headers_4)
 .check(status.is(200))
 )
  
 var loadExisting =
 exec(http("load mac")
 .get("/computers/19")
 .headers(headers_4)
 .check(status.is(200))
 )
 
 var updateExisting =
 exec(http("update computer")
 .post("/computers/19")
 .headers(headers_4)
 .formParam("name", "COSMAC VIP")
 .formParam("introduced", "1977-01-02")
 .formParam("discontinued", "")
 .formParam("company", "3")
 .check(status.is(200))
 )
 }
 
 val simpletest = scenario("scenario 1")
 .repeat(1) {
 feed(csv("users.csv").circular)
 .exec(
 TestHelper.searchFailing
 ,TestHelper.searchExisting
 ,TestHelper.loadExisting
 ,TestHelper.updateExisting 
 )
 }
  
 setUp(simpletest.inject(atOnceUsers(1))).protocols(httpProtocol)
 } 

The main thing that is really new is that we have created our own Scala object. This object, TestHelper in my case, can be used to group either a single step or multiple steps. This example is using TestHelper as a holder for all the individual tests but you might want to have different blocks of code that you can use in different tests. In that case you might wish to have login credentials in one block and searching queries or helpers in a second block.

The scenario definition is now very short and it is obvious what this test is doing by the names that have been selected. It is even possible to define many different scenarios and have them all run at the same time, thus, not just testing system load but perhaps also covering different functionality. Perhaps these many different tests provide full or nearly full system coverage.

   setUp(scenario1.inject(rampUsers(10) over (ramp seconds))
       .protocols(HttpConfig.value(baseURL)),
     scenario2.inject(rampUsers(66) over (ramp seconds))
       .protocols(HttpConfig.value(baseURL))
   ) 

This entry was posted in programming and tagged . Bookmark the permalink.