浅谈架构

用面向对象的脚本去触发性能测试,你一定不会想要像第一季那样去一个一个录制脚本,然后排成一排挨着跑。这样做的最大坏处是代码无复用——你要么会做重复的录制操作,亦或是重复编写代码。这是可维护性的最大敌人。
在官方的文档http://gatling.io/docs/2.1.7/advanced_tutorial.html 这个页面的 Step01 和 Step02 处提供了很好的思路。代码如下:

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 users = scenario("Users").exec(Search.search, Browse.browse)
val admins = scenario("Admins").exec(Search.search, Browse.browse, Edit.edit)
setUp(
  users.inject(rampUsers(10) over (10 seconds)),
  admins.inject(rampUsers(2) over (10 seconds))
).protocols(httpConf)

1) 可以看到有三个操作 Search, Browse 和 Edit 被封装为了 object. 关于 scala 的伴生对象 object,官网上有一句话的准确定义: Objects are native Scala singletons. 实际上 object 就理解为一个 static 类就可以了,里面的成员可以直接调用——调用 Search.search 也就直接执行定义好的 search 系列请求。
2) 定义了两种类型的用户。在这里 users 和 admins 实际上是抽象的,只是定义出某种类型的用户具有什么样的功能,只有灵魂,没有肉体。普通 user 不能编辑,只能搜索和浏览,admin 多了编辑功能。
3) setUp 中为 users 和 admins 注入了具体的用户,现在用户变得有血有肉,不再只是灵魂——10 秒钟内产生 10 个普通用户和管理员。

类似于 demo,如果我们要对门户网站作测试,可以将针对页面的操作一起封装为 object,方便维护,就像 PageObject 一样。
仍旧用新浪作为例子。我们设定一大波小学生要到新浪首页玩儿。为了显得真实一些,把这些小学生分为三种类型:一部分是库昊球迷,喜欢 NBA,所以他们访问到首页后,直接进入 NBA 频道;一部分是阿法狗的棋迷,热爱科技,所以到达首页后直接进入科技频道;剩下的漫无目的,属于题主这种从小就很蠢的没想法没目标没理想没未来的小学生,所以就随便搜搜什么看看,来到首页后使用搜索功能。

大致可以确定的是,现在起码是有 4 种类型的操作可以封装:访问首页,访问 NBA 频道,访问科技频道,搜索。我们可以把访问到首页的脚本放置在包的根目录下,即 sina.Access.scala,因为这个用户行为比较特殊——它几乎是所有连续 性行为的起点;然后把后三者封装到 sina.pages.HomePage.scala 中,因为这些操作都是在首页执行的。我们要做如下的事情:
#1. 首先要分别单独录制这几种操作,均放在根目录下,看看脚本。
#2. 改造脚本,将这些原子操作全部存储到 object 中
#3.客户端直接执行封装到 object 中的操作,实现代码复用

首先先录制进入 NBA 频道的操作

注意进入首页并完全加载后,清除掉已录制的所有请求,再点击进入该频道,仅保留此时开始请求以生成脚本,目的是录制单独的进入该频道的操作。


录制完成后,先把脚本晾在一边,继续完成其他几个操作的录制。

现在我们实行改造,先将 Access.scala 进行改造,存储为 object

Access.scala

package sina

import scala.concurrent.duration._

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._

class Access extends Simulation {

    val httpProtocol = http
        .baseURL("http://www.sina.com.cn")
        .inferHtmlResources(BlackList(""".*\.css""", """.*\.js""", """.*\.ico"""), WhiteList())

    val headers_0 = Map("Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

    val headers_4 = Map("Accept" -> "*/*")

    val headers_284 = Map(
        "Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Pragma" -> "no-cache")

    //为简洁省略掉其他uri
    val uri05 = "http://www.sina.com.cn"
   ......

    val scn = scenario("HomePage")
        .exec(http("request_0")
            .get(uri05 + "/")
            .headers(headers_0)
            .resources(http("request_1")
            .get("http://" + uri45 + "/hm.gif?cc=0&ck=1&cl=24-bit&ds=1360x768&ep=77626%2C77626&et=3&fl=20.0&ja=0&ln=zh-CN&lo=0&lt=1457426038&nv=0&rnd=227026994&si=dd4738b5fb302cb062ef19107df5d2e4&st=4&su=http%3A%2F%2Fofflintab.firefoxchina.cn%2F%3Fcachebust%3D20150714&v=1.1.22&lv=2"),
            http("request_2")
            .get(uri13 + "/data.html?1457426259080")
            //为简洁省略大量request
            .resources(http("request_351")
            .get(uri61 + "/i3/160760139309563749/TB2fRfMhXXXXXatXXXXXXXXXXXX_!!43606076-0-saturn_solar.jpg_200x200.jpg")))

    setUp(scn.inject(rampUsers(10) over (10 seconds))).protocols(httpProtocol)
}

我们只是封装页面,说明要做什么操作,而执行这些操作的的工作放在后面专门的 client 脚本中执行。所以需要把操作装到一个 object 中,并在其中定义一个 access 操作。
1). 修改 Access 为 object
2). 去掉 httpProtocol。
3).定义一个 access 变量,指定所发送的请求.
4). 去掉 setUp 的代码

修改后的代码如下:
Access.scala

package sina

import scala.concurrent.duration._

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._

**object Access {**

    **/* val httpProtocol = http
        .baseURL("http://www.sina.com.cn")
        .inferHtmlResources(BlackList(""".*\.css""", """.*\.js""", """.*\.ico"""), WhiteList()) */**

    val headers_0 = Map("Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

    val headers_4 = Map("Accept" -> "*/*")

    val headers_284 = Map(
        "Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Pragma" -> "no-cache")

    //为简洁省略掉其他uri
    val uri05 = "http://www.sina.com.cn"
   ......

    **val access= exec(http("request_0")**
            .get(uri05 + "/")
            .headers(headers_0)
            .resources(http("request_1")
            .get("http://" + uri45 + "/hm.gif?cc=0&ck=1&cl=24-bit&ds=1360x768&ep=77626%2C77626&et=3&fl=20.0&ja=0&ln=zh-CN&lo=0&lt=1457426038&nv=0&rnd=227026994&si=dd4738b5fb302cb062ef19107df5d2e4&st=4&su=http%3A%2F%2Fofflintab.firefoxchina.cn%2F%3Fcachebust%3D20150714&v=1.1.22&lv=2"),
            http("request_2")
            .get(uri13 + "/data.html?1457426259080")
            //为简洁省略大量request
            .resources(http("request_351")
            .get(uri61 + "/i3/160760139309563749/TB2fRfMhXXXXXatXXXXXXXXXXXX_!!43606076-0-saturn_solar.jpg_200x200.jpg")))

    **/* setUp(scn.inject(rampUsers(10) over (10 seconds))).protocols(httpProtocol)*/**
}

按照同样的方法,把另外三个脚本中的请求均封装到新建立的 HomePage.scala 中

接下来专门建立一个脚本文件,放到 sina.clients 包下,取名为 TestSina.scala,代码如下:

编译成功:

运行:

结果:

Note
#1 本文只是浅谈架构,提出其中一种思路(当然可以有其他很多思路),忽略了一些细节。例如每一个频道,baseUrl 均不一样。要进行严谨的测试,需要仔细的修改和审核每一个脚本,保证最后发出请求的合理性。
#2 工具本身的意义远没有背后蕴含的测试思路和方法重要。对于性能测试的测试思路,结果分析方法等希望有路过的大神多多交流,大家共同学习。


↙↙↙阅读原文可查看相关链接,并与作者交流