Gatling是一款基于Scala 開發(fā)的高性能服務(wù)器性能測(cè)試工具,它主要用于對(duì)服務(wù)器進(jìn)行負(fù)載等測(cè)試;想使用Gatling進(jìn)行壓測(cè)的原因之一是想體驗(yàn)一下Scala編程的感覺(jué),玩一下;第二,工作上也確實(shí)有這樣的需求;
壓測(cè)工作簡(jiǎn)單來(lái)說(shuō)就是利用壓測(cè)應(yīng)用,來(lái)測(cè)試一下服務(wù)器的響應(yīng)性能參數(shù);然后把這些工作全部自動(dòng)化,集成到j(luò)enkins中來(lái)運(yùn)行。
整個(gè)工作的子任務(wù)分解可以由下圖來(lái)表示:
壓測(cè)使用的是一個(gè)常見(jiàn)的web應(yīng)用,該web應(yīng)用的具體使用的業(yè)務(wù)場(chǎng)景如下:
針對(duì)該應(yīng)用的壓測(cè)Scala源代碼如下:
文件名:performance.scala
package performance
import scala.concurrent.duration._
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
class Performance extends Simulation {
//用戶名、餐館ID 存儲(chǔ)文件
val user = csv("/root/.jenkins/workspace/testGatling/src/test/scala/data/user.csv").random
val res = csv("/root/.jenkins/workspace/testGatling/src/test/scala/data/restaurant.csv").random
val ip = csv("/root/.jenkins/workspace/testGatling/src/test/scala/data/ip.csv").random
val httpProtocol = http
.baseURL("http://${ip}:8180")
val headers_0 = Map("Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
val scn = scenario("Emenu")
//打開訂餐首頁(yè)
.exec(http("index")
.get("/E_Menu/userlogin/login.jsp")
.headers(headers_0))
//登錄
.pause(1 second,20 second)
.feed(user)
.feed(res)
.exec(http("login")
.post("/E_Menu/userlogin/login")
.headers(headers_0)
.formParam("username", "${username}")
.formParam("password", "${password}")
)
//選擇餐館
.pause(1 second,20 second)
.exec(http("menu")
.get("/E_Menu/menu.action?res_num=${rest_id}")
.headers(headers_0)
)
//點(diǎn)菜
.pause(1 second,20 second)
.exec(addCookie(Cookie("username", "${username}")))
.exec(addCookie(Cookie("res_num", "${rest_id}")))
.exec(addCookie(Cookie("food_num0", "105")))
.exec(addCookie(Cookie("food_num1", "104")))
.exec(addCookie(Cookie("food_num2", "104")))
.exec(addCookie(Cookie("food_num3", "106")))
.exec(addCookie(Cookie("total", "52")))
.exec(http("list") //點(diǎn)完菜,開始訂
.get("/E_Menu/list.action")
.headers(headers_0)
)
//下單
.pause(1 second,20 second)
.exec(http("order")
.get("/E_Menu/order.action?people=5&time=2025-08-31")
.headers(headers_0)
)
// //回首頁(yè)
.pause(1 second,20 second)
.exec(http("restaurant")
.get("/E_Menu/restaurant.action")
.headers(headers_0)
)
//用戶信息
.pause(1 second,20 second)
.exec(http("userinfo")
.get("/E_Menu/userinfo?username=${username}")
.headers(headers_0)
)
//我的訂單
.pause(1 second,20 second)
.exec(http("userorder")
.get("/E_Menu/userorder.action?username=${username}")
.headers(headers_0)
)
//退出
.pause(1 second,20 second)
.exec(http("exit")
.get("/E_Menu/userlogin/login.jsp")
.headers(headers_0))
setUp(scn.inject(atOnceUsers(100))).protocols(httpProtocol)
}
寫完scala代碼,并且保證它在本地可以調(diào)試通過(guò),下一步就是需要將代碼集成進(jìn)jenkins;
先確保jenkins Gatling Plugin在jenkins上被安裝;
之后,寫入用來(lái)執(zhí)行的shell調(diào)控代碼:
#################### 環(huán)境準(zhǔn)備 ####################
pwd
export IP="xx.xx.xx.xx"
export user="root"
export pwd="xxxxxxxxxx"
export tomcat_path="/root/apache-tomcat-7.0.56/bin"
export killtomcat="/root/killtomcat.sh"
#################### 定義函數(shù) ####################
kill_tomcat7(){
expect -c "
spawn ssh $user@$IP sh $killtomcat;
expect {
yes/no { send yes\r;exp_continue }
*password: { send $pwd\r }
};
expect eof;
"
}
start_tomcat(){
expect -c "
spawn ssh $user@$IP sh $tomcat_path/$1;
expect {
yes/no { send yes\r;exp_continue }
*password: { send $pwd\r }
};
expect eof;
"
}
#################### tomcat壓測(cè) ####################
停止tomcat
kill_tomcat7
啟動(dòng)tomcat
start_tomcat startup_non.sh
mvn gatling:execute -Dgatling.simulationClass=performance.Performance
#################### 完畢 ####################
在完成調(diào)試改bug工作之后,可以嘗試運(yùn)行一下,得到壓測(cè)結(jié)果 Gatling report ,然后可以進(jìn)行相關(guān)的數(shù)據(jù)分析,這里不再贅述。
Jenkins 集成 Gatling Report參考截圖如下:
所以具體實(shí)現(xiàn)過(guò)程我這里不多寫了,我重點(diǎn)寫下對(duì)生成的HTML測(cè)試報(bào)告進(jìn)行優(yōu)化。
如果按JMeter默認(rèn)設(shè)置,生成報(bào)告如下:
從上圖可以看出,結(jié)果信息比較簡(jiǎn)單,對(duì)于運(yùn)行成功的case,還可以將就用著。但對(duì)于跑失敗的case,就只有一行assert錯(cuò)誤信息。(信息量太少了,比較難找到失敗原因)
優(yōu)化大致過(guò)程
1、修改jmeter.properties文件,打開一些輸出內(nèi)容開關(guān)(下圖根據(jù)需要選擇相關(guān)項(xiàng),具體就不用多說(shuō)了吧)
2、制定一份自己的輸出模板。(不用默認(rèn)的jmeter_home/extras/jmeter-results-detail-report.xsl模板,也可以網(wǎng)上自己找份。)
3、最后執(zhí)行,生成對(duì)應(yīng)的HTML報(bào)告(一般我們都在linux環(huán)境 下運(yùn)行,語(yǔ)句大致如下,其中my_project_template.xsl就上第2步說(shuō)的定制模板,這個(gè)是網(wǎng)上找的一份。)
xsltproc $jmeter_home/extras/my_project_template.xsl $my_project_workspace/result/jtl/$test_name/${test_name}.jtl > $my_project_workspace/result/html/$test_name/${test_name}.html
最后報(bào)告如下:
優(yōu)化后的HTML報(bào)告,多了接口地址、接口參數(shù)、Headers信息(包括cookie、session),而且有返回結(jié)果。失敗原因一目了然[圖片上傳失敗...(image-9cb8f5-1527240193077)]
最后附上我的jmeter文件樣本:
什么是mock server
mock:英文可以翻譯為模仿的,mock server是我們用來(lái)解除依賴(耦合),假裝實(shí)現(xiàn)的技術(shù),比如說(shuō),前端需要使用某些api進(jìn)行調(diào)試,但是服務(wù)端并沒(méi)有開發(fā)完成這些api,那么前端的工作就被服務(wù)端阻塞了,那么就可以使用mock server假裝實(shí)現(xiàn)這些api,能夠返回特定的數(shù)據(jù),幫助前端進(jìn)行頁(yè)面渲染,當(dāng)然我們?yōu)榱朔奖憧梢孕枰c服務(wù)端進(jìn)行約定,約定接口的內(nèi)容是什么。
restful接口規(guī)范
轉(zhuǎn)接阮一峰老師的博客—RESTful API 設(shè)計(jì)指南:http://www.ruanyifeng.com/blog/2014/05/restful_api.html
Moco-約定uri(一)
moco工具是在github開源的一個(gè)項(xiàng)目,可以使用moco工具搭一個(gè)簡(jiǎn)單的mock server方便我們進(jìn)行調(diào)試,github地址:https://github.com/dreamhead/moco,下載下來(lái)的是一個(gè)jar包,目前的版本是0.11.1,首先我們要編寫一個(gè)config文件,把我們需要“模擬”的請(qǐng)求和響應(yīng)寫入這個(gè)配置文件,配置文件是json格式的,接下來(lái)我們寫一個(gè)比較簡(jiǎn)單的請(qǐng)求,訪問(wèn) localhost:12306/hello 接口,返回一個(gè)純文本“moco”,moco工具約定了12306端口,不必糾結(jié),就跟tomcat約定8080端口類似,config.json文件如下,而且json文件要與moco的jar包放在同一個(gè)文件夾下。比如博主的目錄結(jié)構(gòu):
G:\學(xué)習(xí)資料\mock\moco-runner-0.11.1-standalone.jar G:\學(xué)習(xí)資料\mock\config.json
[
{
"request":
{
"uri":"/hello"
},
"response":
{
"text":"moco"
}
}
]
配置文件比較簡(jiǎn)單,我們請(qǐng)求接口,返回一個(gè)純文本,啟動(dòng)指令:
>java -jar moco-runner-0.11.1-standalone.jar http -p 12306 -c config.json
這里的http就是http協(xié)議, -p 12306 綁定端口號(hào)12306, -c config.json讀config文件
看到以上的表現(xiàn),就說(shuō)明moco已經(jīng)順利啟動(dòng)了,我們?cè)L問(wèn)localhost:12306/hello 看到結(jié)果如下就說(shuō)明mock server順利返回了我們約定的數(shù)據(jù)”moco”
這里寫圖片描述
Moco-約定uri(二)
修改config文件如下,注意這里moco工具能實(shí)時(shí)監(jiān)測(cè)到j(luò)son配置文件的變化,并自行重啟server
這里寫圖片描述
[
{
"request":
{
"uri":"/"
},
"response":
{
"text":"welcome to Moco"
}
},
{
"request":
{
"uri":"/hello"
},
"response":
{
"text":"moco"
}
}
]
接下來(lái)分別訪問(wèn)localhost:12306和12306:12306/hello,結(jié)果如下:
Moco-約定get請(qǐng)求
[
{
"request":
{
"method":"get",
"uri":"/get"
},
"response":
{
"text":"moco get"
}
}
]
Moco-約定post請(qǐng)求
[
{
"request":
{
"method":"post",
"uri":"/post"
},
"response":
{
"text":"moco post"
}
}
]
Moco-約定請(qǐng)求參數(shù)
[
{
"request":
{
"method":"get",
"uri":"/get",
"queries":
{
"id":"12306",
"name":"moco"
}
},
"response":
{
"text":"moco queries"
}
}
]
Moco-約定請(qǐng)求body必須為json格式
[
{
"request":
{
"method":"post",
"uri":"/post",
"text":
{
"json":"{\"id\":\"12306\",\"name\":\"moco\"}"
}
},
"response":
{
"status":"200"
}
}
]
Moco-約定請(qǐng)求頭部
[
{
"request":
{
"method":"post",
"uri":"/post",
"headers":
{
"content-type":"application/json",
"Connection":"keep-alive",
"Content-Encoding":"gzip"
}
},
"response":
{
"status":"200"
}
}
]
Moco-約定返回內(nèi)容
前面已經(jīng)看到了response的集中返回內(nèi)容如text,和status,下面展示一下返回文件和設(shè)置文件格式等
[
{
"request":
{
"method":"post",
"uri":"/post",
},
"response":
{
"file":"data.js",
"charset":"GBK",
"version":"HTTP/1.0"
}
}
]
Moco-約定返回狀態(tài)碼
見(jiàn)上述的幾個(gè)json,里面已經(jīng)包含了返回狀態(tài)碼的使用方式
Moco-在單元測(cè)試中的使用(以Python為例)
[
{
"request":
{
"method":"get",
"uri":"/api/hello"
},
"response":
{
"text":"hello Savitar!",
"status":200
}
}
]
這里模擬一個(gè)get請(qǐng)求,返回純文本“hello Savitar!”和狀態(tài)碼200,先在瀏覽器訪問(wèn)localhost:12306/api/hello 結(jié)果如下圖:
說(shuō)明接口返回沒(méi)問(wèn)題,接下來(lái)使用Python requests+unittest寫一個(gè)簡(jiǎn)單的接口測(cè)試用例
#coding=utf-8
'''
@author=Savitar
'''
import unittest
import requests
class MocoTestApi(unittest.TestCase):
def setUp(self):
self.url = "http://localhost:12306"
def test_moco_test_api(self):
api = "/api/hello"
url = self.url+api
r = requests.get(url)
self.assertEqual(r.status_code,200)
self.assertEqual(r.text,"hello Savitar!")
def tearDown(self):
pass
if __name__ == '__main__':
unittest.main()








