Gatling模拟真实用户行为是抛弃固定脚本,引入随机性、动态数据和非固定步骤。需要通过 Random、Feeder 和循环控制来实现。
Random注入不确定性
Gatling的Random对象是模拟用户操作随机性。
随机等待时间
真实用户在操作间会有停顿,Gatling通过pause结合Random来模拟。
import scala.concurrent.duration._ // 1. 固定范围均匀分布:在2到5秒之间随机暂停(单位默认秒) pause(2, 5) // 2. 更专业的指定分布:使用Random对象生成 pause(Random.nextInt(5).seconds) // 0-4秒随机整数秒 pause(Random.nextDouble(5).seconds) // 0.0-5.0秒随机浮点数秒 pause(Random.nextGaussian(10, 2).seconds) // 均值10秒,标准差2秒的正态分布建议:对于浏览列表页等情形,使用 uniform;对于“思考时间”这种集中在一定范围内的,可使用 normal;exponential 可用于模拟某些等待事件。
随机选择
模拟用户非固定的操作途径。
import scala.util.Random // 1. 随机选择一项:从序列中随机选一个 val randomSearchKeyword = Random.shuffle(List("手机", "电脑", "平板", "耳机")).head exec(http("Search_${keyword}") .get("/search") .queryParam("q", randomSearchKeyword)) // 2. 按权重随机选择(随机决定树):模拟30%的用户点击详情,70%继续浏览 randomSwitch( 30.0 -> exec(http("View_Detail").get("/detail/${productId}")), 70.0 -> exec(http("Continue_Browsing").get("/list?page=${nextPage}")) )Feeder数据驱动和用户身份多样化
Feeder是为每个虚拟用户提供独特、真实测试数据的机制。
Feeder类型和选择方法
CSV Feeder:csv("users.csv").circular,从CSV读取,circular(循环用)、random(随机用)、queue(用完失败)。最常用。
JSON Feeder:jsonFile("data.json").random,读取JSON,适合嵌套数据结构。
JDBC Feeder:jdbcFeeder(...),从数据库直接读取,数据最新但可能给数据库加压。
Array/Map Feeder:Array(Map("foo"->"bar")).random,直接在代码中定义小型数据集,灵活。
内联JSON:jsonUrl("""[{"id":1}]""").random,测试脚本内嵌数据,无需外部文件。
文章来源:卓码软件测评
精彩推荐:点击蓝字即可
▲软件负载测试▲API自动化测试▲软件测试▲第三方软件测试▲软件性能测试▲软件测试机构
用法和性能优化
// 1. 数据转换和清洗:在Feeder链中即时处理数据 val cleanUserFeeder = csv("users.csv") .transform { case (key, value) => if (key == "email") (key, value.toLowerCase) else (key, value) } .circular // 2. 组合Feeder:合并用户身份数据和业务数据 val combinedFeeder = csv("users.csv").random .and(jsonFile("products.json").random) // 3. 【重点优化】共享Feeder和解耦:在Simulation顶层定义,避免重复读取 object Feeders { val sharedProductFeeder = csv("products.csv").circular } class MySimulation extends Simulation { val scn = scenario("Scenario") .feed(Feeders.sharedProductFeeder) // 所有情形共享同一数据源 .exec(...) }循环创建动态用户会话流
循环控制操作序列的重复执行方式,是模拟用户不断交互的重点。
1. 基础循环
// 1. 固定次数循环:重复执行5次搜索操作 repeat(5) { exec(http("Search").get("/search?q=test")) .pause(1) } // 2. 使用动态变量控制循环:每个用户的循环次数不同(从Session中取) repeat("${desiredLoopCount}") { // 从Feeder或前置操作中获取变量 exec(...) }2. 高级循环和退出条件
// 1. 条件循环(while):模拟用户“刷到满意为止”的行为 asLongAs(session => session("hasFoundTarget").as[Boolean] == false) { exec(http("Next_Page").get("/list?page=${nextPage}")) .pause(2) // 需要在此循环内部的某个操作中,可能将 hasFoundTarget 设置为 true .exec(session => session.set("hasFoundTarget", Random.nextBoolean())) // 示例:随机决定是不是找到 } // 2. 时间条件循环(during):模拟用户在固定时间段内的不断活动(如1分钟内不断操作) during(1 minute) { // 重点:during块内的执行总时间会被控制 exec(http("Do_Something").get("/api")) .pause(Random.nextInt(5).seconds) // 每次操作后随机等待 // 注意:整个during块的时长 = 内部所有exec和pause时间的总和,直到达到1分钟 } // 3. 永远循环(forever):模拟长时间在线的用户,一般配合 exitHereIfFailed 使用 forever { exec(http("Polling").get("/notifications")) .pause(5 seconds) .exitHereIfFailed // 如果轮询失败,则此虚拟用户退出 }实战模拟用户行为
融合了上述所有概念的完整示例,模拟一个用户从登录到下单的真实、非固定行为。
import scala.concurrent.duration._ import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.util.Random class AdvancedEcommerceSimulation extends Simulation { // --- 1. 定义Feeder --- // 用户账户数据 val userFeeder = csv("data/users.csv").circular // 商品数据,随机使用 val productFeeder = csv("data/products.csv").random // 搜索重点词,随机使用 val keywordFeeder = Array( Map("keyword" -> "iPhone"), Map("keyword" -> "MacBook"), Map("keyword" -> "AirPods") ).random val httpProtocol = http.baseUrl("https://api.zmtests.com") // --- 2. 定义情形行为链 --- val scn = scenario("Realistic E-commerce User") // A. 用户登录:每个虚拟用户从Feeder获取唯一身份 .feed(userFeeder) .exec( http("Login") .post("/login") .formParam("username", "${username}") .formParam("password", "${password}") .check(status.is(200)) .check(jsonPath("$.token").saveAs("authToken")) // 保存token供后续使用 ) .pause(Random.nextInt(3).seconds) // 登录后随机短暂停留 // B. 浏览行为:随机循环浏览多个页面 .repeat(Random.nextInt(3) + 1) { // 浏览1-3个页面 feed(keywordFeeder) .exec( http("Search Product") .get("/search") .queryParam("q", "${keyword}") .header("Authorization", "Bearer ${authToken}") .check(jsonPath("$.products[0].id").optional.saveAs("firstProductId")) ) .pause(Random.nextDouble(2, 5).seconds) // 浏览搜索结果随机时间 // 随机决定:是不是查看第一个商品详情? .randomSwitch( 40.0 -> exec( // 40%的概率查看详情 http("View Product Detail") .get("/product/${firstProductId}") .header("Authorization", "Bearer ${authToken}") .pause(Random.nextInt(4).seconds) ), 60.0 -> exec { session => session } // 60%的概率跳过,什么都不做 ) } // C. 模拟加购到下单:使用条件循环,模拟可能放弃的行为 .feed(productFeeder) .exec( http("Add to Cart") .post("/cart") .header("Authorization", "Bearer ${authToken}") .body(StringBody("""{"productId": "${productId}", "quantity": 1}""")) .asJson ) .pause(Random.nextGaussian(10, 3).seconds) // “犹豫时间”,正态分布 // 条件循环:模拟用户可能在结算前反复修改购物车 .asLongAs(session => session("abandonCheckout").as[Boolean] == false) { exec( http("Checkout Preview") .get("/checkout/preview") .header("Authorization", "Bearer ${authToken}") ) .pause(2 seconds) .randomSwitch( 80.0 -> exec( // 80%的概率继续下单 http("Confirm Order") .post("/order") .header("Authorization", "Bearer ${authToken}") .check(status.is(201)) .exec(session => session.set("abandonCheckout", true)) // 订单创建成功,退出循环 ), 20.0 -> exec( // 20%的概率放弃或修改 randomSwitch( 50.0 -> exec(http("Remove Item").delete("/cart/item/1")), // 50%*20%=10% 概率删除商品 50.0 -> exec { session => session.set("abandonCheckout", true) } // 50%*20%=10% 概率直接放弃 ) ) ) } // D. 下单后随机浏览或退出 .randomSwitch( 30.0 -> exec( // 30%的用户继续浏览其他页面 http("Browse Recommendations") .get("/recommendations") .header("Authorization", "Bearer ${authToken}") .pause(Random.nextInt(10).seconds) ), 70.0 -> exec( // 70%的用户直接退出 pause(5 seconds) // 退出前的停留 ) ) // --- 3. 设置负载模型 --- setUp( scn.inject( rampUsersPerSec(1).to(10).during(2 minutes), // 2分钟内逐渐增加到每秒10个用户 constantUsersPerSec(10).during(5 minutes) // 然后保持10用户/秒不断5分钟 ) ).protocols(httpProtocol) }建议和规避
Session状态管理:
陷阱:在repeat、during内部使用 .feed(feeder.random) 可能导致每次迭代使用不同的Feeder数据,破坏情形连续性。
正确做法:如果需要在循环内使用同一用户的不同数据(就像一个用户查多个商品),应在循环外部 .feed 一次,在循环内部使用 ${变量} 引用;如果需要在循环内使用全新数据(如每次搜索新重点词),则需在循环内 .feed。
循环和时间的准确控制:
during和forever:during保证总执行时间不超过指定时长,而forever会无限循环,一般需要配合 exitHereIfFailed或全局时间限制。
负载精确:在during块内,Gatling只控制第一个虚拟用户进入和最后一个虚拟用户退出的时间差,不保证每个用户在整个期间不断活动。更精确的不断负载需靠inject注入方法控制。
随机性和可重复性:
调试:在脚本开发阶段,使用 Random.setSeed(123L) 固定随机数种子,使每次运行结果可复现。
生产压测:移除种子,保证真正的随机性。
Gatling通过Random模拟微观不确定性,通过Feeder提供宏观数据多样性,再通过循环和条件思路将这些元素编织成动态、真实的用户会话流。