本文是记录一个模拟用户下单的压测脚本,从用户登录开始,到创建订单结束。根据线上环境反馈的页面访问比率,在加入购物车这个模块,将区分首页,商品详情页,搜索页(列表页)这三个场景,使用randomSwitch(possibilities: (Double, ChainBuilder)*)
随机命中一个场景,将商品加入购物车。在结算时,将使用rendezVous(users: Int)
来模拟高并发。
使用 Maven 运行 Gatling 脚本,pom.xml
配置如下:
4.0.0
ocg-Gatling
Gatling-maven
1.0-SNAPSHOT
1.8
1.8
UTF-8
2.3.0
2.2.4
io.gatling.highcharts
gatling-charts-highcharts
${gatling.version}
test
net.alchim31.maven
scala-maven-plugin
3.3.2
io.gatling
gatling-maven-plugin
${gatling-plugin.version}
UTF-8
一、编写Request
类
因为项目需要,在接口中设置了公参,且接口请求需要在header
中设置登录token
传递用户信息,为避免每次接口复设置参数,特将请求单独处理。
package conf
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.http.request.builder.HttpRequestBuilder
/**
* Created by chenbo on 2018/9/11.
*/
object Request {
/**
* 处理 post 请求,设置 Authorization , Common parameters
* @param url 请求链接
* @param data 请求参数,类型:Map[String,Any]
* @return
*/
def post(url:String,data:Map[String,Any]):HttpRequestBuilder={
http(url).post(Config.root+url)
.formParamMap(data++params)
.header("Authorization","${token}")
.check(status.is(200))
}
/**
* 无请求参数的 post 请求
* @param url 请求链接
* @return 返回一个
*/
def post(url:String):HttpRequestBuilder={
post(url,Map())
}
/**
* 处理 get 请求,设置 Authorization , Common parameters
* @param url 请求链接
* @param data 请求参数,类型:Map[String,Any]
* @return
*/
def get(url:String,data:Map[String,Any]):HttpRequestBuilder={
http(url).get(Config.root+url+parameter(data++params))
.header("Authorization","${token}")
.check(status.is(200))
}
/**
* 无请求参数的 get 请求
* @param url 请求链接
* @return 返回一个
*/
def get(url:String):HttpRequestBuilder={
get(url,Map())
}
/**
* 处理 put 请求,设置 Authorization , Common parameters
* @param url 请求链接
* @param data 请求参数,类型:Map[String,Any]
* @return
*/
def put(url:String,data:Map[String,Any]):HttpRequestBuilder={
http(url).put(Config.root+url)
.formParamMap(data++params)
.header("Authorization","${token}")
.check(status.is(200))
}
/**
* 无请求参数的 put 请求
* @param url 请求链接
* @return 返回一个
*/
def put(url:String):HttpRequestBuilder={
put(url,Map())
}
/**
* 将参数拼接到Url中
* @param parameter 请求参数
* @return 返回一个Url字符串
*/
private[this] def parameter(parameter:Map[String,Any]):String={
var p = "?"
parameter.keys.foreach(
i => p = p + i +"="+parameter(i)+"&"
)
return p.substring(0,p.length-1)
}
/**
* 设置 Common parameters
*/
private[this] var params:Map[String,Any] = Map(
"version"-> Config.version,
"citySysNo"->Apply.apply.city,
"webSiteSysNo"->Apply.apply.webSite,
"uuk"-> Config.uuk
)
}
二、编写接口请求
import io.gatling.core.Predef._
import io.gatling.core.structure.ChainBuilder
import io.gatling.http.Predef._
import conf._
object Users {
def token:ChainBuilder ={
val data = Map()
exec(_.set("token","Basic **********************"))
.exec(
Request.post(Config.Token,data)
.check( jsonPath("$.msg").is("success"))
.check( jsonPath("$..access_token").saveAs("access_token") )
).exec(session => session.set("token","bearer "+session("access_token").as[String]))
}
}
object Shopping {
def shopping:ChainBuilder= exec(
Request.get( Config.Shopping )
.check( jsonPath("$..msg").is("success") )
.check( jsonPath("$..orderId").saveAs("orderId") )
.check( jsonPath("$.value.deliveryTypeList[*].value").ofType[Int].findAll.saveAs("value") )
)
}
jsonPath("$.value.deliveryTypeList[*].value")
将会得的一个序列,需要使用.findAll
提取全部数据,否则存入session
的将会是第一个元素。
.check( jsonPath("$.value.deliveryTypeList[*].value").ofType[Int].findAll.saveAs("value") ) // value -> Vector(-1, 1, 2, 3)
.check( jsonPath("$.value.deliveryTypeList[*].value").ofType[Int].saveAs("value") ) // value -> -1
使用 util
包处理json数据,需要import scala.util.parsing.json._
.exec(session=>session.set("deliveryType",JSON.parseFull(session("value").as[Int]).get.asInstanceOf[Seq[Int]]))
三、设置方案
import io.gatling.core.Predef._
import scala.concurrent.duration._
/**
* Created by chenbo on 2018/9/6.
*/
class Api_Test extends Simulation{
val jsonFeeder = jsonFile("data/token/unionId.json").random
/*****************************************************/
/*** 使用预先处理的商品,排除异常商品引起的购买失败 ****/
/*****************************************************/
val productFeeder = jsonFile("data/product/product_3.json").random
/**
*
*/
val demo_test = scenario("test")
.feed(jsonFeeder)
.feed(productFeeder)
/** 第一步:用户登录,之后随机选择场景加入购物车 */
.exec(Users.token)
.exec(Users.user_me)
.pause(1,20 seconds)
/** 根据线上受访页百分比,设置随机访问链 */
.randomSwitch(
// 首页查看商品加入购物车
44d -> exec(Users.home,Cart.add_cart),
// 进入商详页加入购物车
39d -> exec(Product.product_detail,Cart.add_cart)
// 17d -> search
)
.pause(1,20 seconds)
/** 访问购物车 */
.exec(Cart.cart)
.pause(1,20 seconds)
/** 模拟线上活动时的并发效果,设置集合点*/
/** 进入结算页,如果没有默认配送时间,选择配送时间,创建订单 */
.rendezVous(50)
.exec(
exec(Shopping.shopping)
.foreach("${value}","index"){
doIf(session=>session("index").as[Int] == -1){
exec( Shopping.ship_time )
}
}
.exec(Shopping.shopping_create_order)
)
/** foreach 方法还可以使用 Expression[Seq[Any]]函数来取值 */
foreach(session => session("value").validate[Seq[Int]],"index"){
exec()
}
setUp(
demo_test.inject(
atOnceUsers(1)
// constantUsersPerSec(1) during( 0.5 minutes)
)
)
}
rendezVous(50)
将在第一次达到 50 个用户后释放,rendezVous
之后的操作链都拥有集合点效果。
.foreach("${value}","index")
将在session
中取属性值value
,遍历每一个元素,以属性名index
保存到session
中。在操作链中使用${index}
或session=>session("index").as[Int]
获取。