性能测试工具 Gatling 官网翻译之高级教程

joshua · 发布于 2016年01月12日 · 最后由 ylq 回复于 2017年06月07日 · 111 次阅读
本帖已被设为精华帖!

写在前面的话:

最近在学习gatling,在网上找了找资料,主要是gatling的官网和stackoverflow。中文的资料一般都特别基础和简单。
翻译gatling的官方文档是因为自己看过了官网资料,觉得特别适合入门,就分享给大家。本人英语渣渣,翻译得天马行空,请大家多见谅。
更新情况,视个人时间,争取每周发两篇。
任何意见和建议都请留言。

后续可能会做一个gatling的测试框架,不过这个就不一定什么时候了……
欢迎转载,不过请注明出处。

原文请移步:http://gatling.io/docs/2.1.7/advanced_tutorial.html#advanced-tutorial

在这一部分,我们假设读者已经完成了前面Quickstart的学习部分。而且你已经有了一个基础的simulation。
我们在这一部分将通过一系列的重构,向读者介绍更多Gatling的高级用法和DSL结构。

回顾下已有的Simulation:

package computerdatabase
import io.gatling.core.Predef._ 
import io.gatling.http.Predef._
import scala.concurrent.duration._
class BasicSimulation extends Simulation { 
  val httpConf = http
    .baseURL("http://computer-database.gatling.io") 
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    .doNotTrackHeader("1")
    .acceptLanguageHeader("en-US,en;q=0.5")
    .acceptEncodingHeader("gzip, deflate")
    .userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0")
  val scn = scenario("BasicSimulation")
    .exec(http("request_1")
    .get("/"))
    .pause(5)
  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)
}

Step 1. 将进程独立出来

现在,我们的simulation脚本,是一大整块的场景。
首先,我们得把它拆分成组合在一起的各个进程,就像Selenium里面的Page Object 模式一样(译者简单解释下Selenium的Page Object Pattern:核心就是实现页面与元素分离,方便扩展和维护)。这样做的好处是,你可以轻松地服用某些部分去完成复杂的操作,而不用牺牲程序的稳定性。

在我们的场景中,我们有三个彼此分开的进程(其实就是要测试的三个场景):
Search:用名字搜索 models;
Browse :以list的形式浏览你的models;
Edit : 编辑特定的model;

我们会把它们提取出来,放到对象里面。这里的对象,指的是Scala的单例;你可以把它们创建到指定的文件里面,或者和Simulation放到一起。

object Search {
  val search = exec(http("Home") // let's give proper names, as they are displayed in the reports
    .get("/"))
    .pause(7)
    .exec(http("Search")
    .get("/computers?f=macbook"))
    .pause(2)
    .exec(http("Select")
    .get("/computers/6"))
    .pause(3)
}
object Browse {
  val browse = ???
}
object Edit {
  val edit = ???
}

我们现在就可以用可重用的事务进程,重写我们的脚本:

val scn = scenario("Scenario Name").exec(Search.search, Browse.browse, Edit.edit)

Step 2. 配置虚拟用户(Vuser)

看看我们的脚本,只有一个虚拟用户(Vuser),你是在逗我吗?
怎么办,加呗!

我们定义两个用户群体:
普通用户:他们只能search和browse models
管理员用户:他们能search、browse和edit models

用脚本实现的场景是这样的:

val users = scenario("Users").exec(Search.search, Browse.browse)
val admins = scenario("Admins").exec(Search.search, Browse.browse, Edit.edit)

下面这段话就是用来设置虚拟用户的,里面的那个10:

setUp(users.inject(atOnceUsers(10)).protocols(httpConf))

这样,我们就设置了10个并发用户。我们就这10个用户,因为我们不想一下子就挂掉server。Server是人类的好朋友,慈悲点有好处的。
你要是有3000个并发用户的话,估计你也不想一下子就全都启动。因为,真实的用户行为也是逐渐地进行访问的(译者:一般的性能测试工具做不到同时起来那么多Vuser)。

Gatling提供一种叫做 rampUsers 的虚拟用户可以模拟这种行为。ramp user 就是指,被一点一点排着队启动的虚拟用户。
下面这么写就是设置了10个普通用户和2个管理用户,他们每人工作10秒钟,因为我们要善待服务器:

setUp(
  users.inject(rampUsers(10) over (10 seconds)),
  admins.inject(rampUsers(2) over (10 seconds))
).protocols(httpConf)

Step 3. 使用Feeder实现数据动态化

现在我们已经知道了如何创建一大堆不同权限的虚拟用户了。但有个问题,他们都在搜索一个model啊,太傻了,得让他们干点不同的事啊!
我们得弄点动态的数据,让我们的Vuser实现不同的场景,并以不同的方式结束。这就是我们为什么需要Feeder。

Feeder究竟是啥?其实Feeder就是你想用的动态数据源。一种最简单常见的Feeder就是CSV。我们下面就看看怎么用CSV存储数据:
首先,我们建一个叫做search.csv的文件,放在user-files/data文件夹下面。
这个文件里面就如下3行东西:

searchCriterion,searchComputerName
Macbook,MacBook Pro
eee,ASUS Eee PC 1005PE

现在,我们就用上面的数据做一个动态的Feeder。

object Search {
  val feeder = csv("search.csv").random // 1, 2
  val search = exec(http("Home")
    .get("/"))
    .pause(1)
    .feed(feeder) // 3
    .exec(http("Search")
    .get("/computers?f=${searchCriterion}") // 4
    .check(css("a:contains('${searchComputerName}')", "href").saveAs("computerURL"))) // 5
    .pause(1)
    .exec(http("Select")
    .get("${computerURL}")) // 6
    .pause(1)
}

解释下:

  1. 首先,我们从search.csv里面定义了一个feeder,里面有两列:searchCriterion和searchComputerName;
  2. 因为feeder的默认使用方式是线性的,用两次就用完了,然后就该挂球了。我们使用random去随机取值,来避免这种情况;
  3. 每次调用的时候,searchCriterion和searchComputerName两个变量被随机取值;
  4. 我们使用Gatling中的EL(表达式语言,就是${****}这种写法的东东)方式来参数化搜索;
  5. 我们使用CSS selector的方式去定位HTML的返回值,这里的写法是超链接,然后把它存到用户会话中,也就是computerURL。你可以看到Scala三重引用的好处——你不必像很多其他语言那样,使用反斜杠\把你的正则表达式弄得乱七八糟;
  6. 我们用这个已经定位的computerURL,就得到了一个确切的网址。

想了解更多的Feeder相关,点这个:http://gatling.io/docs/2.1.7/session/feeder.html#feeder
想了解更多的Http Check相关,点这个:http://gatling.io/docs/2.1.7/session/feeder.html#feeder

Step 4. 循环

我们发现,在browse这个进程中,我们有大量的操作都是通过页面循环,这样就带来了很多重复。我们使用了不同的请求参数发了4次相同的请求。
这就违反了DRY(别写重复代码)原则,下面我们来优化这点:

首先,我们把重复的exec抽出来,封装成一个函数。Simulation是一个典型的Scala类,所以你可以使用任何Scala的语法(译者特别讨厌作者无休止地安利Scala)。

object Browse {
  def gotoPage(page: Int) = exec(http("Page " + page)
    .get("/computers?p=" + page))
    .pause(1)
  val browse = exec(gotoPage(0), gotoPage(1), gotoPage(2), gotoPage(3), gotoPage(4))
}

上面的代码通过页面的号码访问,这就减少了代码重复。
但是下面这个写法更臭屁:

object Browse {
  val browse = repeat(5, "n") { // 1
    exec(http("Page ${n}")
      .get("/computers?p=${n}")) // 2
      .pause(1)
  }
}

解释下:

  1. 这里的repeat是一个runtime类封装好的循环方法,这里就是循环5次,然后保存到用户会话中;
  2. 这里我们使用EL方法来实现访问第几页;

了解更多循环,请访问:http://gatling.io/docs/2.1.7/general/scenario.html#scenario-loops

Step 5. check的使用和场景失败的处理

目前为止,我们一直在使用check来从html返回中校验一些数据。但其实check是可以检查网络状态码的。
check的默认值就是检查20X和304.

为了介绍Gatling的失败处理机制,我们使用check检查一个随机失败的脚本

import java.util.concurrent.ThreadLocalRandom // 1
val edit = exec(http("Form")
  .get("/computers/new"))
  .pause(1)
  .exec(http("Post")
  .post("/computers")
  .check(status.is(session => 200 + ThreadLocalRandom.current.nextInt(2)))) // 2

解释下:

  1. 我们先引用ThreadLocalRandom来生成随机值;
  2. 我们用一个lambda函数去定义一个返回值。这里的lambda函数总返回200或者201。因为返回的状态码是200,所以当检查201的时候,就是check失败了;

管理这种随机失败的场景,我们需要使用两个叫做tryMax和exitHereIfFailed的东东,大致结构如下:

val tryMaxEdit = tryMax(2) { // 1
  exec(edit)
}.exitHereIfFailed // 2

解释下:

  1. tryMax的意思是,如果执行多少次失败才算失败,比如例子中就是两次;
  2. exitHereIfFailed的意思就是认定了以失败作为场景的check结果;

场景条件相关点击这里:http://gatling.io/docs/2.1.7/general/scenario.html#scenario-conditions

下载官方安装包后,这些代码都会在user-files/simulations里面,读者可以看得到。

就这些了,学会了就是Gatling高级用户了(译者觉得懂了这些其实才刚刚开始而已)!

共收到 6 条回复
394
joshua · 1楼 · 2016年01月12日 作者

Quick Start的部分另有高手翻译过,不再赘述了。
想知道Quick Start部分的同学,请移步:http://gatling.io/docs/2.1.7/quickstart.html

4291

有试过单机并发最大数吗

394
joshua · 3楼 · 2016年01月13日 作者

#2楼 @13651969749 没测试过单机的最大并发。不过试过单机1000个同时启动的用户,没有问题,应该够用了。

1928

感谢分享。

7261

最近在研究gatling,写一个参数化Simulation后,启动多个Vuser的话,这些Vuser都会把参数文件中的参数全部使用,不能设置每个Vuser使用其中一个参数。不知道有没有参数可以控制。

96

如果httpconf里面参数可变如何实现,我目前遇到个问题,session里面获取的参数值没办法传入httpconf里面,报No attribute named 'login_token' is defined

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册