当前位置: 首页 > 测试知识 > Gatling的随机性与真实性模拟:使用Random、Feeder与循环实现真实用户行为
Gatling的随机性与真实性模拟:使用Random、Feeder与循环实现真实用户行为
2026-01-13 作者cwb 浏览次数5

Gatling模拟真实用户行为是抛弃固定脚本,引入随机性、动态数据和非固定节奏。这主要通过 Random、Feeder 和循环控制来实现。


Random注入不确定性

Gatling的Random对象是模拟用户操作随机性。


随机等待时间

真实用户在操作间会有停顿,Gatling通过pause结合Random来模拟。


scala

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 可用于模拟某些等待事件。


随机选择

模拟用户非固定的操作途径。


scala

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,测试脚本内嵌数据,无需外部文件。


用法和性能优化


scala

// 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. 基础循环


scala

// 1. 固定次数循环:重复执行5次搜索操作

repeat(5) {

  exec(http("Search").get("/search?q=test"))

  .pause(1)

}


// 2. 使用动态变量控制循环:每个用户的循环次数不同(从Session中取)

repeat("${desiredLoopCount}") { // 从Feeder或前置操作中获取变量

  exec(...)

}


2. 高级循环和退出条件


scala

// 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 // 如果轮询失败,则此虚拟用户退出

}


实战模拟电商用户行为

一个融合了上述所有概念的完整示例,模拟一个电商用户从登录到下单的真实、非固定行为。


scala

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 提供宏观数据多样性,再通过循环和条件思路将这些元素编织成动态、真实的用户会话流。

文章标签: 软件测试 测试工具
咨询软件测试