一、Scenario基础概念
Scenario定义
Scenario是Gatling中的概念,用于模拟真实用户在系统中的完整操作流程。每个Scenario代表一类用户的行为模式。
scala
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class RealUserSimulation extends Simulation {
// HTTP配置
val httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json")
.userAgentHeader("Gatling/3.9")
.disableCaching
// 定义用户操作流程
val userJourney = scenario("真实用户操作流")
.exec(http("首页访问")
.get("/home")
.check(status.is(200))
.check(jsonPath("$.userId").saveAs("userId")))
.pause(2, 5) // 随机思考时间2-5秒
.exec(http("登录操作")
.post("/login")
.formParam("username", "test_user")
.formParam("password", "password123")
.check(status.is(200))
.check(jsonPath("$.authToken").saveAs("authToken")))
.pause(1, 3)
}
二、复杂用户行为建模
多步骤业务流程
scala
val ecommerceUser = scenario("电商购物用户")
.exec(
// 第一阶段:浏览阶段
group("浏览阶段") {
exec(http("访问首页")
.get("/")
.check(status.is(200)))
.pause(3, 7)
.exec(http("浏览商品列表")
.get("/products?category=electronics")
.check(status.is(200))
.check(css(".product-item", "data-id").findAll.saveAs("productIds")))
.pause(2, 4)
.exec(http("查看商品详情")
.get("/products/${productIds.random()}")
.check(status.is(200)))
.pause(5, 10) // 仔细阅读商品详情
}
)
.exec(
// 第二阶段:购买阶段
group("购买阶段") {
exec(http("添加购物车")
.post("/cart/add")
.header("Authorization", "Bearer ${authToken}")
.body(StringBody("""{"productId": "${productId}", "quantity": 1}"""))
.asJson
.check(status.is(201)))
.pause(1, 2)
.exec(http("查看购物车")
.get("/cart")
.header("Authorization", "Bearer ${authToken}")
.check(status.is(200))
.check(jsonPath("$.totalPrice").saveAs("cartTotal")))
.pause(2, 3)
.exec(http("结算")
.post("/checkout")
.header("Authorization", "Bearer ${authToken}")
.body(StringBody("""{"paymentMethod": "credit_card"}"""))
.asJson
.check(status.is(200))
.check(jsonPath("$.orderId").saveAs("orderId")))
}
)
条件分支逻辑
scala
val conditionalUser = scenario("条件流程用户")
.exec(http("访问网站")
.get("/")
.check(status.is(200)))
.pause(2)
// 条件分支:检查用户是否登录
.doIf(session => !session("userId").asOption[String].isDefined) {
exec(http("新用户注册")
.post("/register")
.formParam("email", "user_${userId}@test.com")
.formParam("password", "Test123!")
.check(status.is(201))
.check(jsonPath("$.userId").saveAs("userId")))
.pause(1)
}
// 根据用户类型执行不同操作
.doIfEquals("${userType}", "premium") {
exec(http("访问VIP专区")
.get("/vip/content")
.check(status.is(200)))
.pause(5, 10)
}
.doIfEquals("${userType}", "regular") {
exec(http("浏览普通内容")
.get("/regular/content")
.check(status.is(200)))
.pause(2, 5)
}
// 循环浏览多个页面
.repeat(5, "pageNum") {
exec(http("浏览第${pageNum}页")
.get("/content?page=${pageNum}")
.check(status.is(200)))
.pause(1, 2)
}
三、高级用户行为模式
数据驱动测试
scala
// 使用Feeder提供测试数据
val userFeeder = csv("data/users.csv").circular
val productFeeder = jsonFile("data/products.json").random
val dataDrivenUser = scenario("数据驱动用户")
.feed(userFeeder)
.feed(productFeeder)
.exec(http("使用动态数据登录")
.post("/login")
.formParam("username", "${username}")
.formParam("password", "${password}")
.check(status.is(200))
.check(jsonPath("$.sessionId").saveAs("sessionId")))
.pause(2)
.exec(http("查看个性化推荐")
.get("/recommendations?userId=${userId}")
.header("Session-Id", "${sessionId}")
.check(status.is(200))
.check(jsonPath("$.recommendations[*].productId").findAll.saveAs("recommendedProducts")))
.pause(3)
.foreach("${recommendedProducts}", "product") {
exec(http("查看推荐产品${product}")
.get("/products/${product}")
.check(status.is(200)))
.pause(1)
}
复杂业务流程
scala
val businessWorkflowUser = scenario("复杂业务流程用户")
.tryMax(3) { // 重试机制
exec(
group("文档处理流程") {
exec(http("上传文档")
.post("/documents/upload")
.header("Authorization", "Bearer ${token}")
.bodyPart(RawFileBodyPart("file", "test.pdf"))
.check(status.is(202))
.check(jsonPath("$.documentId").saveAs("docId")))
.pause(1)
.asLongAs(session =>
session("processingStatus").asOption[String].exists(_ != "COMPLETED"),
"processingStep"
) {
exec(http("检查处理状态")
.get("/documents/${docId}/status")
.check(status.is(200))
.check(jsonPath("$.status").saveAs("processingStatus")))
.pause(2)
}
.exec(http("下载处理结果")
.get("/documents/${docId}/download")
.check(status.is(200)))
.pause(3)
}
)
}.exitHereIfFailed // 如果失败则退出场景
四、真实用户模拟配置
多用户类型混合
scala
// 定义不同用户类型的场景
val casualUser = scenario("轻度用户")
.exec(commonActions.randomPageVisits(1, 3))
.pause(30, 60)
val powerUser = scenario("重度用户")
.exec(commonActions.deepEngagementFlow)
.pause(10, 20)
.exec(commonActions.complexOperations)
.pause(5, 15)
val adminUser = scenario("管理员用户")
.exec(adminActions.dashboardAccess)
.pause(2, 5)
.exec(adminActions.userManagement)
.pause(1, 3)
.exec(adminActions.systemMonitoring)
.pause(5, 10)
// 混合用户注入策略
setUp(
casualUser.inject(
rampUsers(100).during(2.minutes), // 前2分钟逐步增加100个轻度用户
constantUsersPerSec(2).during(5.minutes) // 然后保持每秒2个用户
),
powerUser.inject(
rampUsers(20).during(1.minute), // 逐步增加20个重度用户
constantUsersPerSec(0.5).during(10.minutes)
),
adminUser.inject(
constantUsersPerSec(0.1).during(15.minutes) // 持续有管理员操作
)
).protocols(httpProtocol)
.maxDuration(15.minutes)
.assertions(
global.responseTime.max.lt(2000),
global.successfulRequests.percent.gt(95)
)
用户思考时间和行为随机化
scala
val realisticUser = scenario("带思考时间的真实用户")
.exec(api.login)
// 正态分布思考时间
.pause(normalPauses(5.seconds, 2.seconds))
.exec(api.browseProducts)
// 均匀分布思考时间
.pause(uniformPauses(3.seconds, 7.seconds))
.randomSwitch(
70.0 -> exec(api.addToCart), // 70%概率添加购物车
20.0 -> exec(api.saveForLater), // 20%概率收藏
10.0 -> exec(api.continueBrowsing) // 10%概率继续浏览
)
.pause(2, 5)
.randomSwitch(
60.0 -> exec(api.checkout), // 60%概率结账
40.0 -> exec(api.abandonCart) // 40%概率放弃购物车
)
// 使用会话变量控制流程
.doIf(session => session("cartValue").as[Double] > 100) {
exec(api.applyCoupon) // 购物车价值大于100时使用优惠券
.pause(1)
}
.uniformRandomSwitch(
exec(api.standardShipping), // 随机选择配送方式
exec(api.expressShipping),
exec(api.pickupInStore)
)
五、性能测试
模块化设计
scala
object UserActions {
val browseProducts = exec(
group("产品浏览") {
exec(http("获取产品列表")
.get("/api/products")
.queryParam("page", "${currentPage}")
.check(status.is(200))
.check(jsonPath("$[*].id").findAll.saveAs("productIds")))
.pause(1)
.foreach("${productIds}", "productId") {
exec(http("查看产品详情")
.get("/api/products/${productId}")
.check(status.is(200)))
.pause(0.5, 1.5)
}
}
)
val searchProducts = exec(
group("产品搜索") {
feed(searchKeywordsFeeder)
.exec(http("搜索产品")
.get("/api/search")
.queryParam("q", "${keyword}")
.queryParam("sort", "${sortBy}")
.check(status.is(200)))
.pause(2, 4)
}
)
val purchaseFlow = exec(
group("购买流程") {
exec(http("创建订单")
.post("/api/orders")
.body(StringBody(
"""{
| "items": ${cartItems},
| "shippingAddress": "${address}",
| "paymentMethod": "${paymentMethod}"
|}""".stripMargin))
.asJson
.check(status.is(201))
.check(jsonPath("$.orderId").saveAs("orderId")))
.pause(1)
.exec(http("确认订单")
.post("/api/orders/${orderId}/confirm")
.check(status.is(200)))
}
)
}
// 组合使用模块
val completeUserJourney = scenario("完整用户旅程")
.exec(UserActions.browseProducts)
.pause(3)
.exec(UserActions.searchProducts)
.pause(2)
.exec(UserActions.purchaseFlow)
错误处理和恢复
scala
val resilientUser = scenario("弹性用户")
.exec(http("访问首页")
.get("/")
.check(status.is(200))
.check(header("X-Request-ID").saveAs("requestId")))
.pause(2)
// 处理可能的错误
.tryMax(2, "登录重试") {
exec(http("用户登录")
.post("/login")
.body(StringBody("""{"username":"${user}", "password":"${pass}"}"""))
.asJson
.check(status.in(200, 201))
.check(jsonPath("$.token").saveAs("authToken")))
}.exitHereIfFailed
.doIf(session => session("authToken").asOption[String].isDefined) {
exec(http("获取用户资料")
.get("/profile")
.header("Authorization", "Bearer ${authToken}")
.check(status.is(200))
// 如果失败,回退到基础数据
.checkIf(status.is(200)) {
jsonPath("$.premiumFeatures").saveAs("features")
}.checkIf(status.is(404)) {
jsonPath("$.basicFeatures").saveAs("features")
})
}
// 记录错误但不中断测试
.exec(session => {
if (session.isFailed) {
println(s"请求失败但继续执行: ${session("requestId").asOption[String]}")
}
session
})
六、监控验证
验证和断言
scala
val validatedScenario = scenario("带验证的用户流程")
.exec(
http("创建资源")
.post("/api/resources")
.body(StringBody("""{"name": "test-resource", "type": "document"}"""))
.asJson
.check(status.is(201))
.check(jsonPath("$.id").saveAs("resourceId"))
.check(jsonPath("$.createdAt").transform(dateStr =>
java.time.Instant.parse(dateStr).toEpochMilli
).saveAs("creationTime"))
.check(header("Location").saveAs("resourceLocation"))
)
.pause(1)
.exec(
http("验证资源创建")
.get("${resourceLocation}")
.check(status.is(200))
.check(jsonPath("$.id").is("${resourceId}"))
.check(jsonPath("$.status").is("active"))
.check(jsonPath("$.name").is("test-resource"))
// 验证时间戳在合理范围内
.check(jsonPath("$.createdAt").transform(dateStr =>
val created = java.time.Instant.parse(dateStr).toEpochMilli
val now = System.currentTimeMillis()
(now - created) < 60000 // 创建时间应在1分钟内
).is(true))
)
.exec(
http("执行复杂操作")
.put("/api/resources/${resourceId}/process")
.check(status.is(202))
.check(header("X-Operation-Id").notNull)
.check(bodyString.saveAs("responseBody"))
// 自定义验证
.check(bodyString.transform(body => {
val json = io.circe.parser.parse(body).getOrElse(io.circe.Json.Null)
val status = json.hcursor.downField("status").as[String].getOrElse("")
status == "processing" || status == "queued"
}).is(true))
)
性能监控点
scala
val monitoredScenario = scenario("带监控的用户流程")
.exec(
group("关键业务事务") {
exec(
http("事务开始")
.get("/api/transaction/start")
.check(status.is(200))
).pause(1)
.exec(
http("处理步骤1")
.post("/api/transaction/step1")
.check(status.is(200))
.check(responseTimeInMillis.lt(1000)) // 响应时间断言
)
.exec(
http("处理步骤2")
.post("/api/transaction/step2")
.check(status.is(200))
.check(responseTimeInMillis.lt(2000))
)
.exec(
http("事务提交")
.post("/api/transaction/commit")
.check(status.is(200))
.check(jsonPath("$.transactionId").saveAs("txId"))
)
}.resources(
// 监控相关资源加载
http("加载CSS")
.get("/static/css/app.css"),
http("加载JS")
.get("/static/js/app.js")
)
)
七、电商平台用户模拟案例
scala
class ECommerceSimulation extends Simulation {
val httpProtocol = http
.baseUrl("https://ecommerce.example.com")
.acceptHeader("application/json")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0")
.disableCaching
// 数据供给
val userAccounts = csv("data/users.csv").circular
val products = jsonFile("data/products.json").random
val searchTerms = csv("data/search_terms.csv").random
// 用户行为对象
object Browse {
val homepage = exec(http("访问首页")
.get("/")
.check(status.is(200))
.check(css("meta[name='csrf-token']", "content").saveAs("csrfToken")))
.pause(2, 5)
val category = exec(
feed(products)
.exec(http("浏览分类")
.get("/category/${categoryId}")
.check(status.is(200))
.check(css(".product-card", "data-id").findRandom.saveAs("viewedProduct")))
.pause(3, 8)
)
val productDetail = exec(
exec(http("查看产品详情")
.get("/product/${viewedProduct}")
.check(status.is(200))
.check(css("#product-price").saveAs("productPrice")))
.pause(5, 15) // 仔细查看产品
)
}
object Search {
val performSearch = exec(
feed(searchTerms)
.exec(http("搜索产品")
.get("/search")
.queryParam("q", "${searchTerm}")
.check(status.is(200))
.check(css(".search-result-item").count.saveAs("resultCount")))
.pause(1, 3)
)
val filterResults = exec(
exec(http("筛选结果")
.get("/search/filter")
.queryParam("q", "${searchTerm}")
.queryParam("priceMin", "0")
.queryParam("priceMax", "100")
.check(status.is(200)))
.pause(2, 4)
)
}
object Cart {
val addToCart = exec(
exec(http("添加到购物车")
.post("/cart/add")
.header("X-CSRF-Token", "${csrfToken}")
.formParam("productId", "${viewedProduct}")
.formParam("quantity", "1")
.check(status.is(200))
.check(jsonPath("$.cartTotal").saveAs("cartTotal")))
.pause(1, 2)
)
val viewCart = exec(
exec(http("查看购物车")
.get("/cart")
.check(status.is(200))
.check(css(".cart-item").count.gt(0)))
.pause(2, 3)
)
}
object Checkout {
val startCheckout = exec(
exec(http("开始结账")
.post("/checkout/start")
.header("X-CSRF-Token", "${csrfToken}")
.check(status.is(200))
.check(jsonPath("$.checkoutId").saveAs("checkoutId")))
.pause(1, 2)
)
val submitOrder = exec(
exec(http("提交订单")
.post("/checkout/${checkoutId}/complete")
.header("X-CSRF-Token", "${csrfToken}")
.body(StringBody("""{
"shippingMethod": "standard",
"paymentMethod": "credit_card",
"billingAddress": ${addressJson}
}""")).asJson
.check(status.is(200))
.check(jsonPath("$.orderNumber").saveAs("orderNumber")))
.pause(2, 5)
)
}
// 定义完整用户流程
val standardUser = scenario("标准用户")
.feed(userAccounts)
.exec(Browse.homepage)
.randomSwitch(
60.0 -> exec(Browse.category),
40.0 -> exec(Search.performSearch)
)
.pause(2, 5)
.exec(Browse.productDetail)
.randomSwitch(
30.0 -> exec(Cart.addToCart), // 30%的用户添加购物车
70.0 -> exec(Search.performSearch) // 70%继续浏览
)
.doIf(session => session("cartTotal").asOption[String].isDefined) {
exec(Cart.viewCart)
.randomSwitch(
50.0 -> exec(Checkout.startCheckout), // 50%结账
50.0 -> exec(session => {
println(s"用户放弃购物车: ${session("userId").as[String]}")
session
})
)
}
// 设置负载模式
setUp(
standardUser.inject(
nothingFor(5.seconds), // 热身期
rampUsers(50).during(30.seconds), // 逐步增加
constantUsersPerSec(2).during(5.minutes), // 稳定负载
rampUsersPerSec(2).to(10).during(2.minutes), // 压力测试
constantUsersPerSec(10).during(3.minutes), // 峰值负载
rampUsersPerSec(10).to(2).during(1.minute) // 恢复期
)
).protocols(httpProtocol)
.maxDuration(15.minutes)
.assertions(
global.responseTime.percentile3.lt(800), // 99%请求<800ms
global.responseTime.max.lt(3000),
global.failedRequests.percent.lt(1.0),
forAll.responseTime.percentile4.lt(500) // 所有请求的95%<500ms
)
}
八、总结
设计原则
真实:模拟真实用户行为,包括思考时间、错误处理、随机选择
模块化:将常用操作封装为可重用组件
数据驱动:使用外部数据源模拟多样化用户
渐进加载:从低负载开始,逐步增加压力
监控验证:添加断言和监控点
调试技巧
scala
// 添加调试信息
val debugScenario = scenario("调试场景")
.exec(session => {
println(s"当前用户: ${session("userId").asOption[String]}")
println(s"会话属性: ${session.attributes}")
session
})
.exec(http("测试请求")
.get("/debug")
.check(bodyString.saveAs("response"))
.check(status.is(200)))
.exec(session => {
println(s"响应内容: ${session("response").as[String]}")
session
})
// 使用Gatling Recorder记录真实用户操作
// gatling-recorder --help
性能优化建议
连接池配置:优化HTTP连接重用
资源清理:及时关闭不用的连接
缓存策略:合理使用缓存减少重复请求
断言优化:避免过于复杂的断言影响性能