使用Gatling进行性能测试

Gatling下载是一款开源的性能测试工具,提供简洁、强大的DSL API编写测试套件,支持插件扩展,且自动生成美观、明了的HTML测试报告。

Gatling现在最新版本是:2.1.7。需要scala 2.11支持。

Gatling有多种执行方式,可以使用官话的bundle包,使用gatling.sh脚本执行,也可以使用使用gatling-sbt下载Sbt工程里执行,也可以集成到如Jenkins这样的持续集成工具里执行。

使用Gatling

http://gatling.io/#/download下载Gatling bundle 2.1.7,下载后解压,目录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
yangjing-mac-air:gatling-charts-highcharts-bundle-2.1.7 $ tree -d -L 3
├── bin
│ gatling.bat
│ gatling.sh
│ recorder.bat
│ recorder.sh
├── conf
├── lib
│   └── zinc
├── results
├── target
│   └── test-classes
│   └── computerdatabase
└── user-files
├── bodies
├── data
└── simulations
└── computerdatabase

bin目录的下放了两种启动脚本,分别有Unix LikeWindows环境的脚本 。gatling.sh是执行测试的脚本,它会列出已有的所有测试文件,并选择执行。而recorder.sh是一个图形化的Gatling脚本配置工具,通过很简洁的界面开始编写基础的Gatling测试脚本。

lib目录下是Gatling所依赖的库文件。

用户编写的测试脚本放在user-files/simulations目录下,Gatling已经自带了一个测试样例供参考。user-files/data目录可以放一些测试需要的测试数据,比如用户名和密码等。Gatling提供了对CSV文件的良好支持,可以把一组测试用户数据保存在一个CSV文件里,由Gatling读取。

最终生成的测试报告将会放在results目录。

Gatling自带了几个测试脚本,可以执行./bin/gatling.sh运行。这可以做为一个很好的开始Gatling测试的方式。

sbt项目中使用Gatling

在平常的测试、开发中,每次都把测试脚本放到 user-files/simulations 目录,并通过gatling.sh脚本执行,感觉有些不方便。我们可以使用 Gatling sbt-pluginGatling很方便的集成到sbt工程里。

示例工程地址:https://github.com/yangbajing/gatling-example

sbt工程配置

project/plugins.sbt 添加gatling插件

1
addSbtPlugin("io.gatling" % "gatling-sbt" % "2.1.7")

project/Build.scala 添加gatling依赖库

1
2
"io.gatling.highcharts" % "gatling-charts-highcharts" % "2.1.7" % "provided,test"
"io.gatling" % "gatling-test-framework" % "2.1.7" % "test"

Gatling测试脚本的编写

每个gatling测试脚本都需要继承Simulation抽象类,同时还要导入以下包:

1
2
import io.gatling.core.Predef._
import io.gatling.http.Predef._

编写Gatling测试脚本

一般gatling脚本的编写分为3部分:

  1. 定义 http protocols:定义能用的http header选项,设置要测网址的baseURL。
  2. 编写 scenario: 每个剧本定义了一套测试脚本,使用exec执行实际的测试动作。
  3. 设置 scenario并执行:使用setUp装载多个scenario,并设置同时多少个用户并发访问,以及访问方式(在一个时间点内同时并发还是在一段时间内渐进并发)等。

http protocol

1
2
3
4
5
6
7
http
.baseURL("http://localhost:19001")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
.doNotTrackHeader("1")
.acceptLanguageHeader("zh-CN,zh;q=0.8,en;q=0.6")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36")

baseUrl用于设置待测试网址的域名,其它设置是为了模拟浏览器的请求头。

scenario

scenario用于定义一组测试脚本,使用exec命令来执行一个HTTP Request。多个HTTP Request可以链式调用。

1
2
3
val helloscalaSDKApiScenario = scenario("HelloscalaSDKApi")
.feed(externalIds)
.exec(createUser, socialSites, userLabels)

feed用于导入用户定义数据,可以从文件导入,也可以编程生成。Gatling默认提供了csvtsvssvjsonFile等多种文件导入方法。feed也支持传入一个Seq[Map[String, T]]类型的用户生成数据。接下来会用编程方式生成一堆测试用户ID。

.exec设置要执行的测试请求,输入类型主要有两类:XXXXBuilderExpression[Session]XXXXBuilder用于加入测试请求,Expression[Session]用于设置Gatling的Session状态。定义的几个测试请求动作代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 生成1000个测试externalId
val externalIds = (1 to 1000).map(_ => Map("externalId" -> org.bson.types.ObjectId.get.toString))

val createUser = resetTimestamp
.exec(
http("createUser")
.post("/api/external/user")
.header("sc-apikey", APIKEY)
.header("sc-timestamp", "${timestamp}")
.header("sc-api-token",
session => computeToken("post", session("timestamp"), "/api/external/user"))
.body(StringBody(
"""{
| "externalId":"${externalId}",
| "name":"用户1",
| "idCard":"50038119850000000X",
| "corporation":"企业1"
|}""".stripMargin)).asJSON
.check(status.is(201))
)

val socialSites = resetTimestamp
.exec(
http("postSocialSites")
.post("/api/external/user/socialSites")
.header("sc-apikey", APIKEY)
.header("sc-timestamp", "${timestamp}")
.header("sc-api-token",
session => computeToken("post", session("timestamp"), "/api/external/user/socialSites"))
.body(StringBody(
"""{
| "externalId": "${externalId}",
| "socialSites":[{
| "socialSite": "QQ",
| "accessToken": "lskjdflksdjflksdjf",
| "openId": "24234324",
| "expireIn": "23423"
| }]
|}""".stripMargin)).asJSON
.check(status.is(200))
)

val userLabels = resetTimestamp
.exec(
http("userLables")
.get("/api/external/user/labels")
.header("sc-apikey", APIKEY)
.header("sc-timestamp", "${timestamp}")
.header("sc-api-token",
session =>
computeToken("get", session("timestamp"), "/api/external/user/labels",
"externalId=" + session("externalId").as[String])
)
.queryParam("externalId", "${externalId}")
.check(status.is(200))
)

先来看看resetTimestamp做了什么工作,

1
def resetTimestamp = exec(session => session.map(_.set("timestamp", System.currentTimeMillis().toString)))

因为是对REST风格的一个API作压力测试,而API接入文档要求每次接入时都需要根据sharedSecrethttp methodapi pathtimestampquery string做一个Hash,再将生成的token存入http header

而这段代码的意思就是当前时间戳存到Gatling Session中,这样在每次exec调用前都重置下Session中的timestamp变量,可以更好的模拟真实的API调用环境。

${timestamp}这样的语法是Gatling提供的EL,见:http://gatling.io/docs/2.1.7/session/expression_el.html。使用字符串插值的方式可以方便的获取到Gatling Session里保存的变量,包括用feed导入的用户数据。

再看userLabels这个方法,这里有特色的一部分是对sc-api-token的设置。对于一个基于REST风格的API,在每次请求时都进行访问校验是一种常用方式,而这个sc-api-token的计算就由computeToken函数来完成。computeToken函数定义如下:

1
2
3
def computeToken(method: String, timestamp: SessionAttribute, apiPath: String, queryString: String = "") = {
Utils.hexSha256(method.toLowerCase + SHARED_SECRET + timestamp.as[String] + apiPath + queryString)
}

但是在这里,我们不能直接把"${timestamp}""externalId=${externalId}"当作字符串参数传给computeToken函数,因为computeToken函数的执行并不在Gatling Session的执行上下文中。我们必需在调用computeToken函数之前就获取到Gatling Session中值。

header方法的第二个参数是value: Expression[String],跟踪代码可以看到,Expression的定义是:type Expression[T] = Session => Validation[T]。所以之前的一接传入一个字符串其实是由scala的隐式转换功能将其转换成了下人Expression[String]的类型(Scala implicit)。

这里就传入一个Session => String匿名函数,通过session.apply(key)方法得到一个SessionAttribute,这样就可以获取到Gatling Session里的变量了。而.as[String]方法是获取SessionAttribute变量的值,并取出为String类型。

setUp

使用setUp函数装载写好的测试剧本scenario,并设置请求用户数(并发数)。.protocols设置之前定义的http protocol

1
2
3
setUp(
helloscalaSDKApiScenario.inject(atOnceUsers(userSize))
).protocols(httpConf)

执行测试

sbt控制台里使用test命令执行所有测试脚本,也可以使用testOnly package.Simulation来指定执行某一个脚本。

生成的测试HTML报告将存放在target/galing目录。

================================================================================
---- Global Information --------------------------------------------------------
> request count                                         30 (OK=20     KO=10    )
> min response time                                     11 (OK=11     KO=12    )
> max response time                                     61 (OK=61     KO=19    )
> mean response time                                    20 (OK=23     KO=15    )
> std deviation                                         12 (OK=14     KO=2     )
> response time 50th percentile                         15 (OK=18     KO=14    )
> response time 75th percentile                         22 (OK=27     KO=16    )
> mean requests/sec                                  77.72 (OK=51.813 KO=25.907)
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                            20 ( 67%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                10 ( 33%)
---- Errors --------------------------------------------------------------------
> status.find.is(200), but actually found 404                        10 (100.0%)
================================================================================

Reports generated in 0s.
Please open the following file: /Users/jingyang/workspace/pressure-suite/target/gatling/cloudsimulation-1438759192370/index.html
[info] Simulation CloudSimulation successful.
[info] Simulation(s) execution ended.
[success] Total time: 30 s, completed 2015-8-5 15:19:53

测试结果就不截图了,有兴趣的朋友可以下载源码,并自行修改后运行查看。

总结

Gatling是一个强大、易用、可扩展的性能测试利器。现在支持对httphttpsjmssse等多种协议的支持。Gatling使用Async Http ClientNetty提供非阻塞的HTTP。用Akka管理Action(请求、暂停、断言等),以及建模和测试流程。

分享到