Hi folks, today I'm gonna show you how to loadtest your rest api with gatling tool.
I.Why loadtest:
Load testing gives confidence in the system & its reliability and performance. Load Testing helps identify the bottlenecks in the system under heavy user stress scenarios before they happen in a production environment.
You can find out more about loadtest in this guru99
II.What is gatling:
Gatling is a loadtest tool created based on Scala, Akka and Netty.
You can findout more information about gatling in official gatling site
Gatling provides the DSL (Domain specific language) for us to create the scenario and setup for loadtesting.
Below is the code snippet for a gatling simulation scenario.
class ConcurrentRequests extends BaseSimulation{
val scn: ScenarioBuilder = scenario("Check concurrentRequests for get song detail api")
.exec(
http("Get song detail api")
.get(GeneralConfigs.COCCOCMUSIC_MOBILE_API_SONGS+"/"
+EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_TESTDATA_SONGDETAIL.zingmp3
+"_"+"audio")
.check(status.is(200)))
setUp(scn.inject(nothingFor(4),atOnceUsers(1),rampUsersPerSec(1) to (2) during(10)).protocols(mobileApiProtocol)).maxDuration(100)
.assertions(global.responseTime.max.lt(2000), global.successfulRequests.percent.gt(95))
}
Or you can use the gatling frontline version (commercial version) in here
III.What is Scala:
Scala stands for scalable language which compile to java byte code.
Scala is a multiparadigm language that supports both object-oriented and functional programming style.
You can find out more about Scala in here
IV.Set things up:
1.IntelliJ:
For our loadtest project, we will need to use intelliJ (a product by Jetbrains) which targets supporting JVM languages.
You can download the IntelliJ from here
2.Maven:
We will use maven as a build tool for the project, so you will need to install maven.
Please download maven from here
After download, please set the system path to the bin directory of maven
3.Java jdk:
Please download jdk8 for stable version working with Scala and Gatling.
You can download from here
4.Scala sdk:
You can set the scala sdk directly in IntelliJ.
IntelliJ will automatically found the scala sdk version for you and suggest if you have not done so.
The Scala sdk version you should install is 2.12.10 for stability
V.Get your hands dirty in the real project:
1.Create a maven scala project in IntelliJ:
Go to File -> New project -> tick on create from archetype -> choose scala-archetype-simple
2.Pom file:
We will add dependencies for our project in the pom file.
Also, we add the mechanism so that for each profile we parse into maven commandline, it will choose the correct env config file for us.
The pom file would be like below, a little bit long (:D)
#pom file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>coccoc-music</groupId>
<artifactId>mobile-api-perf</artifactId>
<version>1.0-SNAPSHOT</version>
<inceptionYear>2008</inceptionYear>
<properties>
<scala.version>2.12.10</scala.version>
<gatling.version>3.3.1</gatling.version>
</properties>
<repositories>
<repository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.specs</groupId>
<artifactId>specs</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.gatling</groupId>
<artifactId>gatling-test-framework</artifactId>
<version>3.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.gatling/gatling-core -->
<dependency>
<groupId>io.gatling</groupId>
<artifactId>gatling-core</artifactId>
<version>3.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.gatling.highcharts/gatling-highcharts -->
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-highcharts</artifactId>
<version>3.3.1</version>
<type>pom</type>
</dependency>
<!-- https://mvnrepository.com/artifact/io.gatling.highcharts/gatling-charts-highcharts -->
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-charts-highcharts</artifactId>
<version>3.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.typesafe.play/play-json -->
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-json_2.12</artifactId>
<version>2.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-antrun-plugin -->
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
<build>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>3.0.5</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>dev</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<!-- copy file db of profile and iWealth for test environment -->
<delete file="src/main/resources/env.json"/>
<copy file="src/main/resources/env.dev.json"
tofile="src/main/resources/env.json"/>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>stag</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<!-- copy file db of profile and iWealth for test environment -->
<delete file="src/main/resources/env.json"/>
<copy file="src/main/resources/env.stag.json"
tofile="src/main/resources/env.json"/>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>prd</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<!-- copy file db of profile and iWealth for test environment -->
<delete file="src/main/resources/env.json"/>
<copy file="src/main/resources/env.prd.json"
tofile="src/main/resources/env.json"/>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
3.Project structure:
main package:
We only define the resources file in the main package
The tree is like:
File env.json is the file where we store real env usage for current run
env.{env_name}.json is the files to store env for each environment
general.json is the file to store general config value
test package:
We will have the below package:
- configs To store configuration files to get value from config file in resources folder above
For example : The code below demonstrate how to store config value to the variable and use that in our test script
package coccocMusic.configs
import coccocMusic.models.{Environments, PlaylistDetail, SongDetail}
import utils.FileUtilsInternal
import play.api.libs.json.Json
object EnvironmentConfigs {
val fileUtilsInternal = new FileUtilsInternal
def environment():Environments= {
val jsonEnvironments = fileUtilsInternal.parseFileToJson(System.getProperty("user.dir") + "\\src\\main\\resources\\env.json")
val environmentsValue = Json.fromJson[Environments](jsonEnvironments)
environmentsValue.getOrElse(Environments).asInstanceOf[Environments]
}
final val COCCOCMUSIC_MOBILE_API_DOMAIN: String = environment().coccocMusic.mobile.api.domain
final val COCCOCMUSIC_MOBILE_API_BASICAUTH_USERNAME: String = environment().coccocMusic.mobile.api.basicAuth.username
final val COCCOCMUSIC_MOBILE_API_BASICAUTH_PASSWORD: String = environment().coccocMusic.mobile.api.basicAuth.password
final val COCCOCMUSIC_MOBILE_API_TESTDATA_PLAYLISTDETAIL: PlaylistDetail = environment().coccocMusic.mobile.api.testData.playlistDetail
final val COCCOCMUSIC_MOBILE_API_TESTDATA_SONGDETAIL: SongDetail = environment().coccocMusic.mobile.api.testData.songDetail
final val COCCOCMUSIC_MOBILE_API_TESTDATA_PLAYLISTSBYCATEGORY: Int = environment().coccocMusic.mobile.api.testData.playlistsByCategory
}
- executions: to store our test scenario
for example the scenario for test our home screen api:
package coccocMusic.executions.homeScreen.scenarios
import coccocMusic.configs.GeneralConfigs
import coccocMusic.setup.BaseSimulation
import io.gatling.core.Predef._
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef._
class ConcurrentRequests extends BaseSimulation {
val scn: ScenarioBuilder = scenario("Check concurrentRequests for homescreen api")
.exec(
http("Get playlists and songs")
.get(GeneralConfigs.COCCOCMUSIC_MOBILE_API_HOMESCREEN)
.check(status.is(200)))
val fromUsers: Double = Integer.getInteger("fromUsers",1).toDouble
val toUsers: Double = Integer.getInteger("toUsers",1).toDouble
val duringSeconds: Integer = Integer.getInteger("duringSeconds",1)
setUp(scn.inject(nothingFor(4),atOnceUsers(1),rampUsersPerSec(fromUsers) to (toUsers) during(duringSeconds)).protocols(mobileApiProtocol)).maxDuration(100)
.assertions(global.responseTime.max.lt(2000), global.successfulRequests.percent.gt(95))
}
Above we define the endpoint for our api : GeneralConfigs.COCCOCMUSIC_MOBILE_API_HOMESCREEN
Get the input fromUsers, toUsers, duringSeconds from commandline, auto set to 1 if not defined (-DfromUsers={value} -DtoUsers={value} -DduringSeconds={value}
- models: To define models for the environments and general configurations (Remember we got json file for resources, so models make total sense)
For example like below:
package coccocMusic.models
import play.api.libs.functional.syntax._
import play.api.libs.json.{JsPath, Reads}
import play.api.libs.json._
case class Environments(coccocMusic: CoccocMusic)
case class CoccocMusic(mobile: Mobile)
case class Mobile(api: Api)
case class Api(domain: String, basicAuth: BasicAuth, testData: TestData)
case class BasicAuth(username: String, password: String)
case class TestData(playlistDetail: PlaylistDetail, songDetail: SongDetail, playlistsByCategory: Int)
case class PlaylistDetail(nhaccuatui: String, zingmp3: String)
case class SongDetail(nhaccuatui: String, zingmp3: String)
object BasicAuth{
implicit val basicAuthReads: Reads[BasicAuth] = (
(JsPath \ "username").read[String] and
(JsPath \ "password").read[String]
)(BasicAuth.apply _)
}
object TestData{
implicit val testDataReads: Reads[TestData] = (
(JsPath \ "playlistDetail").read[PlaylistDetail] and
(JsPath \ "songDetail").read[SongDetail] and
(JsPath \ "playlistsByCategory").read[Int]
)(TestData.apply _)
}
object PlaylistDetail{
implicit val playlistDetailReads: Reads[PlaylistDetail] = (
(JsPath \ "nhaccuatui").read[String] and
(JsPath \ "zingmp3").read[String]
)(PlaylistDetail.apply _)
}
object SongDetail{
implicit val songDetailReads: Reads[SongDetail] =(
(JsPath \ "nhaccuatui").read[String] and
(JsPath \ "zingmp3").read[String]
)(SongDetail.apply _)
}
object Api{
implicit val apiReads: Reads[Api] = (
(JsPath \ "domain").read[String] and
(JsPath \ "basicAuth").read[BasicAuth] and
(JsPath \ "testData").read[TestData]
)(Api.apply _)
}
object Mobile{
implicit val mobileReads: Reads[Mobile] =
(__ \ "api").read[Api].map(Mobile.apply)
}
object CoccocMusic{
implicit val coccocMusicReads: Reads[CoccocMusic] =
(__ \ "mobile").read[Mobile].map(CoccocMusic.apply)
}
object Environments{
implicit val coccocMusicReads: Reads[Environments] =
(__ \ "coccocMusic").read[CoccocMusic].map(Environments.apply)
}
- setup :
We will set the BaseSimulation class for after use.
Example code below for setting the domain and basic auth:
package coccocMusic.setup
import coccocMusic.configs.{EnvironmentConfigs, GeneralConfigs}
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.http.protocol.HttpProtocolBuilder
class BaseSimulation extends Simulation{
val mobileApiProtocol: HttpProtocolBuilder = http.baseUrl(EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_DOMAIN
+GeneralConfigs.COCCOCMUSIC_MOBILE_API_PREFIX+GeneralConfigs.COCCOCMUSIC_MOBILE_API_VERSION).basicAuth(
EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_BASICAUTH_USERNAME,EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_BASICAUTH_PASSWORD)
}
- utils: This is where we define the utility we make for our project
Example for the utils to parse the json file to json object:
package utils
import play.api.libs.json.{JsValue, Json}
import scala.io.Source
class FileUtilsInternal {
def parseFileToJson(pathName: String): JsValue = {
val bufferSource=Source.fromFile(pathName)
var jsonFormat: JsValue = null
try {
val source:String=bufferSource.getLines.mkString
jsonFormat = Json.parse(source)
}
finally {
bufferSource.close()
}
jsonFormat
}
}
- Run the scenario:
For example we can run the scenario for home screen api like below:
mvn clean gatling:test -Pdev -Dgatling.simulationClass=coccocMusic.executions.homeScreen.scenarios.ConcurrentRequests -DfromUsers=10 -DtoUsers=14 -DduringSeconds=20
Let me explain in detail
mvn clean : to clean the target folder
gatling:test to run the test
-Pdev : this is to remove the content in env.json file, and parse the content from env.dev.json to env.json (You can definitely change to -Pstag or -Pprd for different environment)
-Dgatling.simulationClass=coccocMusic.executions.homeScreen.scenarios.ConcurrentRequests : this to specify the simulationClass to run the test
-DfromUsers=10 -DtoUsers=14 -DduringSeconds=20 : this is to define the number of users to users, the duration for ramping up the requests
- The report file:
Will look like this where you can see the details for overall or for specific request (located in target/gatling/scenario_name/index.html)
VI.Source code:
The source code for the project you can find here in github
That's it. I hope it helps
Peace as always!!!
Notes: If you feel this blog help you and want to show the appreciation, feel free to drop by :
This will help me to contributing more valued contents.
Happy coding!!!
Top comments (1)
This article is very good.. can you please add the git link it seems like it is broken