前言
Gatling是一款基於Scala 開發的高性能服務器性能測試工具,它主要用於對服務器進行負載等測試,並分析和測量服務器的各種性能指標。目前僅支持http協議,可以用來測試web應用程序和RESTful服務。
除此之外它擁有以下特點:
-
-
支持實時生成Html動態輕量報表,從而使報表更易閱讀和進行數據分析
-
支持DSL腳本,從而使測試腳本更易開發與維護
-
支持錄制並生成測試腳本,從而可以方便的生成測試腳本
-
支持導入HAR(Http Archive)並生成測試腳本
-
支持Maven,Eclipse,IntelliJ等,以便於開發
-
支持Jenkins,以便於進行持續集成
-
支持插件,從而可以擴展其功能,比如可以擴展對其他協議的支持
-
開源免費
依賴工具
-
Maven
-
JDK
-
Intellij IDEA
安裝Scala插件
打開 IDEA ,點擊【IntelliJ IDEA】 -> 【Preferences】 -> 【Plugins】,搜索 “Scala”,搜索到插件然后點擊底部的 【Install JetBrains plugin…】安裝重啟即可。
Gatling Maven工程
創建Gatling提供的gatling-highcharts-maven-archetype,
在 IntelliJ中選擇 New Project
-> Maven
-> Create form archetype
-> Add Archetype
,在彈出框中輸入一下內容:
GroupId: io.gatling.highcharts ArtifactId: gatling-highcharts-maven-archetype Version: 3.0.0-RC3
點擊查看最新版本: 最新版本
之后輸入你項目的GroupId(包名)和ArtifactId(項目名)來完成項目創建,
項目創建完成后,Maven會自動配置項目結構。
注:在創建的工程,修改pom.xml文件,添加如下配置,加快構建速度:
<repositories> <repository> <id>public</id> <name>aliyun nexus</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>public</id> <name>aliyun nexus</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories>
工程項目目錄
工程項目結構如下圖:
項目目錄說明:
-
bodies:用來存放請求的body數據
-
data:存放需要輸入的數據
-
scala:存放Simulation腳本
-
Engine:右鍵運行跟運行
bin\gatling.bat
和bin\gatling.sh
效果一致 -
Recorder:右鍵運行跟運行
bin\recorder.bat
和bin\recorder.sh
效果一致,錄制的腳本存放在scala目錄下 -
target:存放運行后的報告
至此就可以使用IntelliJ愉快的開發啦。
Gatling測試SpringBoot
Gatling基於Scala開發的壓測工具,我們可以通過錄制自動生成腳本,也可以自己編寫腳本,大家不用擔心,首先腳本很簡單常用的沒幾個,另外gatling封裝的也很好我們不需要去專門學習Scala語法,當然如果會的話會更好。
SpringBoot測試工程示例
Maven依賴
代碼如下
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
控制層接口
代碼如下:
@RestController public class HelloWorldController { @RequestMapping("/helloworld") public String sayHelloWorld(){ return "hello World !"; } }
瀏覽器演示
Gatling測試腳本編寫
Gatling基於Scala開發的壓測工具,我們可以通過錄制自動生成腳本,也可以自己編寫腳本,大家不用擔心,首先腳本很簡單常用的沒幾個,另外gatling封裝的也很好我們不需要去專門學習Scala語法,當然如果會的話會更好。
腳本示例
import io.gatling.core.Predef._ import io.gatling.http.Predef._ class SpringBootSimulation extends Simulation{ //設置請求的根路徑 val httpConf = http.baseUrl("http://localhost:8080") /* 運行100秒 during 默認單位秒,如果要用微秒 during(100 millisecond) */ val scn = scenario("SpringBootSimulation").during(100){ exec(http("springboot_home").get("/helloworld")) } //設置線程數 // setUp(scn.inject(rampUsers(500) over(10 seconds)).protocols(httpConf)) setUp(scn.inject(atOnceUsers(10)).protocols(httpConf)) }
腳本編寫
Gatling腳本的編寫主要包含下面三個步驟
-
http head配置
-
Scenario 執行細節
-
setUp 組裝
我們以百度為例,進行第一個GET請求測試腳本的編寫,類必須繼承 Simulation
-
配置下head,只是簡單的請求下百度首頁,所以只定義下請求的base url,采用默認的http配置即可
//設置請求的根路徑 val httpConf = http.baseURL("http://localhost:8080")
-
聲明Scenario,指定我們的請求動作
val scn = scenario("SpringBootSimulation").during(100){ exec(http("springboot_home").get("/helloworld")) }
scenario里的參數:scenario name exec()里的參數就是我們的執行動作,http(“本次請求的名稱”).get(“本次http get請求的地址”)
-
設置並發數並組裝
//設置線程數 setUp(scn.inject(atOnceUsers(10)).protocols(httpConf))
atOnceUsers:立馬啟動的用戶數,可以理解為並發數
這樣我們一個簡單的腳本就完成了,可以運行看下效果。
部分測試報告如下:
高級教程
Injection – 注入
注入方法用來定義虛擬用戶的操作
setUp( scn.inject( nothingFor(4 seconds), // 1 atOnceUsers(10), // 2 rampUsers(10) over(5 seconds), // 3 constantUsersPerSec(20) during(15 seconds), // 4 constantUsersPerSec(20) during(15 seconds) randomized, // 5 rampUsersPerSec(10) to 20 during(10 minutes), // 6 rampUsersPerSec(10) to 20 during(10 minutes) randomized, // 7 splitUsers(1000) into(rampUsers(10) over(10 seconds)) separatedBy(10 seconds), // 8 splitUsers(1000) into(rampUsers(10) over(10 seconds)) separatedBy atOnceUsers(30), // 9 heavisideUsers(1000) over(20 seconds) // 10 ).protocols(httpConf) )
-
nothingFor(duration):設置一段停止的時間
-
atOnceUsers(nbUsers):立即注入一定數量的虛擬用戶
setUp(scn.inject(atOnceUsers(50)).protocols(httpConf))
-
rampUsers(nbUsers) over(duration):在指定時間內,設置一定數量逐步注入的虛擬用戶
setUp(scn.inject(rampUsers(50) over(30 seconds)).protocols(httpConf))
-
constantUsersPerSec(rate) during(duration):定義一個在每秒鍾恆定的並發用戶數,持續指定的時間
setUp(scn.inject(constantUsersPerSec(30) during(15 seconds)).protocols(httpConf))
-
constantUsersPerSec(rate) during(duration) randomized:定義一個在每秒鍾圍繞指定並發數隨機增減的並發,持續指定時間
setUp(scn.inject(constantUsersPerSec(30) during(15 seconds) randomized).protocols(httpConf))
-
rampUsersPerSec(rate1) to (rate2) during(duration):定義一個並發數區間,運行指定時間,並發增長的周期是一個規律的值
setUp(scn.inject(rampUsersPerSec(30) to (50) during(15 seconds)).protocols(httpConf))
-
rampUsersPerSec(rate1) to(rate2) during(duration) randomized:定義一個並發數區間,運行指定時間,並發增長的周期是一個隨機的值
setUp(scn.inject(rampUsersPerSec(30) to (50) during(15 seconds) randomized).protocols(httpConf))
-
heavisideUsers(nbUsers) over(duration):定義一個持續的並發,圍繞和海維賽德函數平滑逼近的增長量,持續指定時間(譯者解釋下海維賽德函數,H(x)當x>0時返回1,x<0時返回0,x=0時返回0.5。實際操作時,並發數是一個成平滑拋物線形的曲線)
setUp(scn.inject(heavisideUsers(50) over(15 seconds)).protocols(httpConf))
-
splitUsers(nbUsers) into(injectionStep) separatedBy(duration):定義一個周期,執行injectionStep里面的注入,將nbUsers的請求平均分配
setUp(scn.inject(splitUsers(50) into(rampUsers(10) over(10 seconds)) separatedBy(10 seconds)).protocols(httpConf))
-
splitUsers(nbUsers) into(injectionStep1) separatedBy(injectionStep2):使用injectionStep2的注入作為周期,分隔injectionStep1的注入,直到用戶數達到nbUsers
setUp(scn.inject(splitUsers(100) into(rampUsers(10) over(10 seconds)) separatedBy atOnceUsers(30)).protocols(httpConf))
循環
val scn = scenario("BaiduSimulation").
exec(http("baidu_home").get("/"))
上面的測試代碼運行時只能跑一次,為了測試效果,我們需要讓它持續運行一定次數或者一段時間,可以使用下面兩個方式:
-
repeat
repeat(times,counterName) times:循環次數 counterName:計數器名稱,可選參數,可以用來當當前循環下標值使用,從0開始
val scn = scenario("BaiduSimulation").repeat(100){ exec(http("baidu_home").get("/")) }
-
during
during(duration, counterName, exitASAP) duration:時長,默認單位秒,可以加單位milliseconds,表示毫秒 counterName:計數器名稱,可選。很少使用 exitASAP:默認為true,簡單的可以認為當這個為false的時候循環直接跳出,可在 循環中進行控制是否繼續
/* 運行100秒 during 默認單位秒,如果要用微秒 during(100 millisecond) */ val scn = scenario("BaiduSimulation").during(100){ exec(http("baidu_home").get("/")) }
POST請求
post參數提交方式:
-
JSON方式
import io.gatling.core.Predef._ import io.gatling.core.scenario.Simulation import io.gatling.http.Predef._ class JsonSimulation extends Simulation { val httpConf = http.baseURL("http://127.0.0.1:7001/tst") //注意這里,設置提交內容type val headers_json = Map("Content-Type" -> "application/json") val scn = scenario("json scenario") .exec(http("test_json") //http 請求name .post("/order/get") //post url .headers(headers_json) //設置body數據格式 //將json參數用StringBody包起,並作為參數傳遞給function body() .body(StringBody("{\"orderNo\":201519828113}"))) setUp(scn.inject(atOnceUsers(10))).protocols(httpConf) }
-
Form方式
import io.gatling.core.Predef._ import io.gatling.http.Predef._ class FormSimulation extends Simulation { val httpConf = http .baseURL("http://computer-database.gatling.io") //注意這里,設置提交內容type val contentType = Map("Content-Type" -> "application/x-www-form-urlencoded") //聲明scenario val scn = scenario("form Scenario") .exec(http("form_test") //http 請求name .post("/computers") //post地址, 真正發起的地址會拼上上面的baseUrl http://computer-database.gatling.io/computers .headers(contentType) .formParam("name", "Beautiful Computer") //form 表單的property name = name, value=Beautiful Computer .formParam("introduced", "2012-05-30") .formParam("discontinued", "") .formParam("company", "37")) setUp(scn.inject(atOnceUsers(1)).protocols(httpConf))
-
RawFileBody
import io.gatling.core.Predef._ import io.gatling.core.scenario.Simulation import io.gatling.http.Predef._ class JsonSimulation extends Simulation { val httpConf = http.baseURL("http://127.0.0.1:7001/tst") //注意這里,設置提交內容type val headers_json = Map("Content-Type" -> "application/json") val scn = scenario("json scenario") .exec(http("test_json") //http 請求name .post("/order/get") //post url .headers(headers_json) //設置body數據格式 //將json參數用StringBody包起,並作為參數傳遞給function body() .body(RawFileBody("request.txt")) setUp(scn.inject(atOnceUsers(10))).protocols(httpConf) }
txt的文件內容為JSON數據,存放目錄
/resources/bodies
下
Feed 動態參數
Gatling對參數的處理稱為Feeder[供料器],支持主要有:
-
數組
val feeder = Array( Map("foo" -> "foo1", "bar" -> "bar1"), Map("foo" -> "foo2", "bar" -> "bar2"), Map("foo" -> "foo3", "bar" -> "bar3"))
-
CSV文件
val csvFeeder = csv("foo.csv")//文件路徑在 %Gatling_Home%/user-files/data/
-
JSON文件
val jsonFileFeeder = jsonFile("foo.json") //json的形式: [ { "id":19434, "foo":1 }, { "id":19435, "foo":2 } ]
-
JDBC數據
jdbcFeeder("databaseUrl", "username", "password", "SELECT * FROM users")
-
Redis
可參看官方文檔http://gatling.io/docs/2.1.7/session/feeder.html#feeder
使用示例:
import io.gatling.core.Predef._ import io.gatling.core.scenario.Simulation import io.gatling.http.Predef._ import scala.concurrent.duration._ /** * region請求接口測試 */ class DynamicTest extends Simulation { val httpConf = http.baseURL("http://127.0.0.1:7001/test") //地區 feeder val regionFeeder = csv("region.csv").random //數組形式 val mapTypeFeeder = Array( Map("type" -> ""), Map("type" -> "id_to_name"), Map("type" -> "name_to_id")).random //設置請求地址 val regionRequest = exec(http("region_map").get("/region/map/get")) //加載mapType feeder .feed(mapTypeFeeder) //執行請求, feeder里key=type, 在下面可以直接使用${type} .exec(http("province_map").get("/region/provinces?mType=${type}")) //加載地區 feeder .feed(regionFeeder) //region.csv里title含有provinceId和cityId,所以請求中直接引用${cityId}/${provinceId} .exec(http("county_map").get("/region/countties/map?mType=${type}&cityId=${cityId}&provinceId=${provinceId}")) //聲明scenario name=dynamic_test val scn = scenario("dynamic_test") .exec(during(180){ regionRequest }) //在2秒內平滑啟動150個線程(具體多少秒啟動多少線程大家自己評估哈,我這里瞎寫的) setUp(scn.inject(rampUsers(150) over (2 seconds)).protocols(httpConf)) }
注意:通過下面的代碼只會第一次調用生成一個隨機數,后面調用不變
exec(http("Random id browse") .get("/articles/" + scala.util.Random.nextInt(100)) .check(status.is(200))
Gatling的官方文檔解釋是,由於DSL會預編譯,在整個執行過程中是靜態的。因此Random在運行過程中就已經靜態化了,不會再執行。應改為Feeder實現,Feeder是gatling用於實現注入動態參數或變量的,改用Feeder實現:
val randomIdFeeder = Iterator.continually(Map("id" -> (scala.util.Random.nextInt(100)))) feed(randomIdFeeder) .exec(http("Random id browse") .get("/articles/${id}")) .check(status.is(200))
feed()
在每次執行時都會從Iterator[Map[String, T]]