Gatling的Session API、EL表达式和随机函数是构建动态、真实负载测试,模拟出每个虚拟用户(Virtual User)的独特行为。
Session :一个不可变键值存储,存储着该用户的所有个性化状态。随机函数和Feeder负责生成数据并写入Session;而EL表达式则是在需要时从Session中读取数据的简洁语法。
二、 Session API
Session是Gatling状态管理的主要对象,是不可变(Immutable) 的。任何修改操作都会返回一个新的Session实例。
操作API:
存储数据:使用 .set(key, value)
scala
.exec { session =>
val updatedSession = session.set("userId", 12345).set("requestId", UUID.randomUUID().toString)
updatedSession // 必须返回这个新的Session
}
读取数据:使用 .get(key),返回 Option[Any]
scala
.exec { session =>
val maybeUserId = session("userId").asOption[Int] // 安全读取,类型转换
val userId = session("userId").as[Int] // 直接读取,类型不匹配或不存在会抛异常
session
}
移除数据.remove(key)
避免使用Session API进行复杂的业务逻辑计算。它主要用于状态传递,复杂逻辑应放在exec的函数体外或使用Gatling的内置处理器。
三、 EL表达式:简洁的动态参数注入
EL表达式(表达式语言)是Gatling DSL中在字符串模板内动态引用Session属性的简洁语法,格式为 ${attributeName}。
应用示例:
URL路径和查询参数:
scala
.get("/api/users/${userId}/orders?type=${orderType}")
请求体(JSON/XML):
scala
.body(StringBody("""{"userId": ${userId}, "items": [${itemId}]}""")).asJson
请求头:
scala
.header("X-Trace-Id", "${traceId}")
特性:
自动类型转换:如果Session中存储的是数字,在JSON体中会被正确渲染为数字(无引号)。
空值安全:如果Session中不存在该属性,Gatling会将该虚拟用户的请求标记为失败。
局限:无法在EL表达式中进行运算或方法调用(如${userId + 1}是无效的)。
四、 随机函数和Feeder数据生成引擎
1. 随机函数库Random
Gatling在io.gatling.core.Predef和io.gatling.core.session.el包中提供了丰富的随机函数,可直接在EL表达式或Session API中使用。
基础随机:
scala
.exec(http("随机用户")
.get("/user/${randomInt(1, 100)}") // 生成1到100之间的整数
.header("X-Random", "${randomUuid}") // 生成随机UUID
)
随机字符串:
scala
"${randomAlphanumeric(10)}" // 10位字母数字
加权随机选择:
scala
"${uniformSample(List("mobile", "desktop", "tablet"))}" // 均匀选择
"${circularSample(List("A", "B", "C"))}" // 循环选择
2. Feeder结构化数据源
Feeder用于从外部文件(CSV、JSON)或内部迭代器中读取数据,并注入Session。
CSV文件驱动:
scala
// users.csv 文件内容
// userId,username
// 1,userA
// 2,userB
val userFeeder = csv("users.csv").random
val scn = scenario("场景")
.feed(userFeeder) // 为每个虚拟用户注入一行数据
.exec(http("获取用户")
.get("/api/${userId}")
.check(jsonPath("$.name").is("${username}"))
)
自定义迭代器(Iterator.continually):
scala
val customFeeder = Iterator.continually(
Map("dynamicId" -> (System.currentTimeMillis() + Random.nextInt(1000)))
)
五、 模拟真实用户下单流程
模拟一个包含用户登录、浏览商品、随机下单的完整业务流程:
scala
import scala.concurrent.duration._
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class AdvancedSimulation extends Simulation {
val httpProtocol = http.baseUrl("https://api.zmtests.com")
// 1. 使用Feeder加载测试账户和商品数据
val userAccounts = csv("data/users.csv").circular
val productPool = csv("data/products.csv").random
val scn = scenario("完整购物流程")
// 2. 为虚拟用户注入初始身份
.feed(userAccounts)
.exec(
http("用户登录")
.post("/login")
.body(StringBody("""{"username":"${username}","password":"${password}"}"""))
.check(jsonPath("$.token").saveAs("authToken")) // 3. 提取Token存入Session
)
.pause(2)
// 4. 循环浏览商品
.repeat(5, "visitCount") {
feed(productPool)
.exec(
http("浏览商品 - ${productId}")
.get("/products/${productId}")
.header("Authorization", "Bearer ${authToken}") // 使用Session中的Token
.check(status.is(200))
)
.pause(1)
}
// 5. 随机决定是否下单(50%概率)
.doIf(session => Random.nextBoolean()) {
exec(
http("创建订单")
.post("/orders")
.header("Authorization", "Bearer ${authToken}")
.body(ElFileBody("templates/order.json")) // 6. 使用模板文件,其中可包含EL表达式
.check(jsonPath("$.orderId").saveAs("createdOrderId"))
)
.exec { session =>
// 7. 使用Session API进行后置处理
println(s"用户 ${session("username").as[String]} 创建了订单:${session("createdOrderId").as[String]}")
session
}
}
setUp(
scn.inject(rampUsers(100).during(30.seconds))
).protocols(httpProtocol)
}
六、实践
性能:避免在Session中存储过大的对象(如整个文件内容),会增加内存开销和序列化。
线程安全:Gatling的随机函数和Feeder在设计上都是线程安全的,无需额外同步。
可重复:调试时可以为随机数生成器设置固定种子(通过Session的seed属性),保证每次运行脚本时生成相同的“随机”序列。
错误处理:对于主要的Session属性,在使用EL表达式前,可先用.get进行安全检查,或使用doIf保证存在。