Gatling簡單測試SpringBoot工程
前言
Gatling是一款基於Scala 開發的高效能伺服器效能測試工具,它主要用於對伺服器進行負載等測試,並分析和測量伺服器的各種效能指標。目前僅支援http協議,可以用來測試web應用程式和RESTful服務。
除此之外它擁有以下特點:
-
支援Akka Actors 和 Async IO,從而能達到很高的效能
-
支援實時生成Html動態輕量報表,從而使報表更易閱讀和進行資料分析
-
支援DSL指令碼,從而使測試指令碼更易開發與維護
-
支援錄製並生成測試指令碼,從而可以方便的生成測試指令碼
-
支援匯入HAR(Http Archive)並生成測試指令碼
-
支援Jenkins,以便於進行持續整合
-
支援外掛,從而可以擴充套件其功能,比如可以擴充套件對其他協議的支援
-
開源免費
依賴工具
-
Maven
-
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
點選檢視最新版本: ofollow,noindex" target="_blank"> 最新版本
之後輸入你專案的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")
-
可參看官方文件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]]
物件中取出一個值,這樣才能實現動態引數的需求。