Performance testing with Gatling
Intro/Agenda Performance testing overview Gatling overview Gatling test structure Gatling API Gatling Recorder Gatling DSL Gatling reports
Performance testing
Measure response time under workload Load, Stress, Soak, Spike testing Find bottlenecks Satisfy impatient customers Increase conversion rate Sustain company reputation
Approach 1.Set proper goals 2.Choose tools 3.Try the tools 4.Implement scenarios 5.Prepare environments 6.Run and measure
Gatling overview
Firepower Gatling
Thousands of concurrent users Users are not threads, but actors Asynchronous concurrency Scala Akka Netty Recorder (HTTP Proxy and HAR) Extensions (Maven, Gradle, Jenkins)
1 thread = 1 user * *
Blocking I/O * *
Actors Model Mathematical model used in parallel computing Actor is autonomous computational unit No shared resources, no shared state Asynchronous message passing Mailbox to buffer incoming messages React on received messages
Gatling test structure
Building blocks
Gatling API
val myHttpGetRequest = http("Open home page").get("/home") HTTP GET Request Define constant variable Variable name Create HTTP Request object Request name Method of HTTP Request object Request URI
val myHttpProtocol = http.baseURL(" HTTP Protocol Create HTTP Protocol object URL is prepended to any HTTP request Application URL
val myScenario = scenario("Browse Home").exec(myHttpGetRequest).pause(2).exec(myReq2) Scenario Create Scenario object Scenario name Execute HTTP Request Already created HTTP Request object Pause time in seconds Pause between requests Execute more HTTP Requests
setUp(myScenario.inject(atOnceUsers(10))).protocols(myHttpProtocol) setUp Gatling Simulation base method Scenario object method Number of users Already created HTTP Protocol object Already created Scenario object Injection profile Configure protocol
import io.gatling.core.Predef._ import io.gatling.http.Predef._ class MySimulation extends Simulation { } Simulation class Gatling base simulation class Import Gatling API classes Simulation class name
Gatling simulation import io.gatling.core.Predef._ import io.gatling.http.Predef._ class MySimulation extends Simulation { val myHttpProtocol = http.baseURL(" val myHttpGetRequest = http("Open home page").get("/home") val myLoginRequest = http("Open login page").get("/login") val myScenario = scenario("Browse Home").exec(myHttpGetRequest).pause(2).exec(myLoginRequest) setUp(myScenario.inject(atOnceUsers(10))).protocols(myHttpProtocol) }
Gatling Recorder
Recorded simulation import io.gatling.core.Predef._ import io.gatling.http.Predef._ class RecordedSimulation extends Simulation { val httpProtocol = http.baseURL(" val scn = scenario("RecordedSimulation").exec(http("request_0").get("/products")).pause(5).exec(http("request_1").get("/products?q=SearchString")) setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol) }
Gatling DSL Domain Specific Language
Gatling DSL Feeders csv, ssv, tsv, jsonFile, jsonUrl, jdbcFeeder queue, random, circular Checks responseTimeInMillis, latencyInMillis, status, currentLocation header, headerRegex bodyString, bodyBytes, regex, xpath, jsonPath, css Check actions find, findAll, count, saveAs is, not, exits, notExists, in, optional
Gatling DSL (2) Execution control doIf, doIfOrElse, doSwitch, doSwitchOrElse, randomSwitch repeat, foreach, during, asLongAs, forever tryMax, exitBlockOnFail, exitHereIfFailed Assertions responseTime, allRequests, requestsPerSec min, max, mean, stdDev, percentile1, percentile2 lessThan, greaterThan, between, is, in, assert Injection profiles nothingFor, atOnceUsers, rampUsers over, rampUsers during
Session A virtual user’s state stores all simulation user’s data Map[String, Any] Write data using Feeders extract and save from response programmatically with Session API Read data using Gatling Expression Language programmatically with Session API
Gatling reports
STIONS ? ANY
Thank you! Lyudmil Latinov Senior Automation QA Xoomworks Bulgaria Blog: AutomationRhapsody.comAutomationRhapsody.com
Bonus slides Offline bonus slides
private val csvFeeder = csv("search_terms.csv").circular.random CSV Feeder Access modifier Read CSV file CSV file name Start over once file is read CSV file content First line is header, used as session variable name search_term prod product1 hello searchTerm1 hello hellosearch_terms.csv Access in random order
val scn = scenario("Search products").feed(csvFeeder).forever() { exec(http("Search product").get("/products?q=${search_term}")) } Use CSV Feeder Add Feeder to Scenario object Already created CSV Feeder object Iterate scenario forever Execute HTTP Requests in body HTTP GET Request Access CSV data by header with Gatling EL
val reqSavePerson = http("Save Person").post("/person/save").body(ElFileBody("person.json")).header("Content-Type", "application/json") HTTP POST Request Create HTTP Post Request object Request URI { "id": "${id}", "firstName": "${first_name}", "lastName": "${last_name}", " ": "${ }" }person.json Add header field Add body to HTTP Post request Read body file and parse with Gatling EL File name Gatling EL parser replaces session variables
val myProtocol = http.baseURL(" val myRequest = http("Home").get("/home").check(regex("Hello, (*.?) (*.?)!")) Checks Checks can be defined on HTTP Protocol level Add check to HTTP Protocol object Response code is 200 OK Usually check are defined on HTTP Request level Add check to HTTP Request object Response body matches regular expression
val reqSearch = http("Search product").get("/products?q=${search_term}").check(regex("Found ([\\d]{1,4}) products:").saveAs("numberOfProducts")) Save to Session HTTP GET Request Access session data with Gatling EL Check response body matches specific regex Save regular expression matched group Name of session variable to save to
val reqOpenProduct = exec(session => { var prds = session("numberOfProducts").as[String].toInt var productId = Random.nextInt(prds) + 1 session.set("productId", productId) }).exec(http("Open").get("/products?id=${productId}")) Manage Session variables Access Session objectinside exec block with lambda Some test logic Execute HTTP Get request Variable is accessible only on second exec block Create and return new Session object with variable saved in it Read variable from Session object as String and convert to Integer
val scnLogoutOrHome = scenario("Logout Or Home").doIfEqualsOrElse("${action}", "Logout") { exec(http("Do logout").get("/logout")) } { exec(http("Home page").get("/home")) } Conditional execution Conditions are calculated on Scenario object If Session variable equals given value Execute Requests chain in case of FALSE Execute Requests chain in case of TRUE
setUp(scnSearch.inject(rampUsers(10) over 10.seconds), scnOpenProduct.inject(atOnceUsers(10))).protocols(myProtocol).maxDuration(10.minutes).assertions( global.responseTime.max.lessThan(800), global.successfulRequests.percent.greaterThan(99) ) Advanced setUp Maximum duration in case of forever() Assert each response time is bellow 800ms Different scenarios with different injection profiles Assert 99% success responses for the Simulation