如何在Gatling负载测试中为多个虚拟用户使用单个OAuth2.0令牌

时间:2019-04-04 17:24:25

标签: scala load-testing gatling gatling-jsonpath

我需要通过Gatling(我是一个新手!)对需要OAuth2.0令牌的API进行负载测试,但希望每个虚拟用户都使用相同的令牌。我正在获取令牌ok(我认为),并将其放入名为“ access”的变量中,但是当测试本身开始时,我不断收到“未定义名为“ access”的属性”。

我的令牌检索如下所示(连同httpConf,在下面使用):

 class MySimulation extends Simulation {

 val httpConf = http        
    .baseUrl("https://MyBaseUrl.Com/")
    .acceptHeader("application/json") 
    .doNotTrackHeader("1")
    .acceptLanguageHeader("en-UK,en;q=0.5")
    .acceptEncodingHeader("gzip, deflate")
    .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
    .shareConnections

 val header = Map("Content-Type" -> """application/x-www-form-urlencoded""")

 al auth = scenario("Retrieve Token")
 .exec(http("POST OAuth Req")
 .post("https://SomeTokenUrl")
 .formParam("resource", "someresource")
 .formParam("grant_type", "somegranttype")
 .formParam("client_secret", "someclientsecret")
 .formParam("client_id", "someclientid")
 .headers(header).check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access"))) 

然后,我尝试将负载测试设置为(注意:我最初确实放置了“ Map”,而不是可变变量,但读取了默认值是不可变的地方,并想知道这是为什么报头无法更新的原因) :

 val headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")

 val scn = scenario("MyService Gatling test run")       
           .exec(http("")               
           .post("Myservice/api")
           .headers(headers_10.toMap)                
           .body(StringBody("""{"SomeProperty": "Some Value"}"""))
           .asJson
           .check(status.is(200)))

 setUp(
    auth.inject(constantUsersPerSec(1) during (2 seconds)),
    scn.inject(nothingFor(2 seconds),
    constantUsersPerSec(10) during (10 seconds) 
    ).protocols(httpConf))
    .assertions(global.responseTime.max.lt(500)) 
    .assertions(forAll.failedRequests.percent.lte(1)) 
    .assertions(global.responseTime.mean.lte(100)) 

这个想法是令牌检索将在负载测试开始之前完成,然后负载测试场景将使用“ access”变量,但是它给出了以下结果:

  ERROR : Failed to build request: No attribute named 'access' is defined 

我已经结束了束缚。我猜想这可能与作用域有关,也许该变量不会延续到负载测试方案中,但是我在其他地方看到了一些示例,这些示例似乎完全推荐这种设置,所以我不知道这些其他示例是否部分完成或是什么。

4 个答案:

答案 0 :(得分:2)

今天,我为我的项目实施了此方案。请参见下面的代码,它也将为您工作。

注意:我已经使用API​​的必需参数编写了此代码。您可以根据需要修改此代码。目前,此代码是在单个类中编写的。我已经使用不同的类和属性文件以适当的格式实现了此代码。我也将发布它。

对于单个类,代码如下:

`

package aapi

    import io.gatling.core.Predef._
    import io.gatling.http.Predef._

     class manifestSimulation extends Simulation {    

    private var token = ""

    object authAvi{
     // This is the request(API) which we are going to use for generating the auth token 1 time and then will feed this token into subsequent request.
     var postBody = "{\"username\":\"devusername\”,\”password\”:\”devpassword”}”

    val auth = scenario("Retrieve our auth Token which will be used in the subsequent request“)
        .exec(
            http("POST OAuth Req")
            .post(“User Post URL“)
            .body(StringBody(postBody))
            .header("ClientId", “test”)
    .header("DSN", “devDB”)
    .header("accept", "application/json")
    .header("Accept-Language", "en-us")
    .header("Content-Type", "application/json")
            .check(status.is(200))
    .check(jsonPath("$.token")
    .saveAs("token")))
            .exitHereIfFailed
            .exec{session => { token = session("token").as[String]
                             session}}       
    }
    object manifest {
     // This is the request(API) which we are going to hit multiple times using the token which we generated from the previous auth API

        var manifestHeaders = Map("ClientId" -> “test”, "DSN" -> "devDB", "Token" -> "${token}")

        val manifestMethod = exec(session => session.set("token", token))            
            .exec(http("Manifest Details")              
                .get(“Your get URL“)
                .headers(manifestHeaders)
                .check(status.is(200))
                 )
    }   

    val scn = scenario(“**********This is your actual load test*******************”)
        .exec(manifest.manifestMethod)
       setUp(
      authAvi.auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
    scn.inject(nothingFor(4 seconds), // waits 4 seconds as a margin to process token and this time varies for every user
        constantUsersPerSec(5) during (5 seconds))) // fire 5 requests per second for 5 seconds which will result in 25 (5*5) requests and overall 26 requests when the report gets generated (because we have 1 request for auth token and 25 requests of our intended API (25+1 = 26)

     }`

答案 1 :(得分:1)

会话是每个用户的会话,并且用户之间不共享会话数据。因此,当您有1个用户运行“ auth”方案并保存令牌时,是两个不同的用户运行“ scn”,他们无权访问auth用户的会话值。

不建议这样做,但是您可以通过将auth令牌推入常规scala var并在auth场景中进行设置并在主场景中读取它来解决此问题-您只需要确保auth始终完成就可以您注入了其他用户。

var token: String = ""

然后在auth场景中,在末尾有一个步骤,例如

.exec(session => {
    token = session.get("access").as[String]
    session
})

然后在scn方案的开始处有一个步骤来设置会话变量

.exec(session.set("access", token))

我过去曾经使用过这种模式,并且可以使用,但是我敢肯定还有更好的方法

答案 2 :(得分:0)

我也在尝试此方案,但是在scn方案开始时尝试添加.exec {session => {token = session(“ access”)。as [String] session}}时出现错误

       val scn = scenario("MyService Gatling test run")
       .exec{session => { token = session("access").as[String] session}}  
      .exec(http("")               
       .post("Myservice/api")
       .headers(headers_10.toMap)                
       .body(StringBody("""{"SomeProperty": "Some Value"}"""))
       .asJson
       .check(status.is(200)))

我还在下面的步骤中分别尝试了更改:-

  setUp(auth.inject(constantUsersPerSec(1) during (2 seconds)),
  .exec{session => { token = session("access").as[String] session}}
  scn.inject(nothingFor(2 seconds),
  constantUsersPerSec(10) during (10 seconds) 
  ).protocols(httpConf))
  .assertions(global.responseTime.max.lt(500)) 
  .assertions(forAll.failedRequests.percent.lte(1)) 
  .assertions(global.responseTime.mean.lte(100)) 

但是他们都不起作用。您能建议我将这段代码实际放在哪里吗?

答案 3 :(得分:0)

@Tarun,

当我这样做的时候,我的场景中有一个“ exec”而不是它的设置,并且使用以下语法:

 val dataToUse = feed(testData)
 .exec(session => session.set("access", token))            
 .exec(http("")             
 .post("*the_URL_to_send_to)*")
 .headers(headers_10.toMap)
 .body(RawFileBody("${filePath}")).asJson
 .check(status.is(200))
             )

如先前讨论中的评论中所述,这是因为我使用的是加特林的更高版本,并且“ get”方法不再是会话api的一部分。

我的完整解决方案如下-请注意,有许多事情可能需要注意,但这可能不适用于您的解决方案。我使用了一个对象,因为它使我在想做的事情变得更加清晰!另外,某些导入可能是多余的,因为我将它们作为散弹枪方法的一部分包括在内,以找到可行的方法!

最后,我基本上列出目录的内容,并循环浏览其中列出的文件,并使用每个目录作为供稿器。您看起来好像在使用json的文字模板,因此可能不需要它,但我想我会出于完整性考虑将其包括在内,因为它非常方便-如果您更改json的格式,则无需您需要弄乱在模拟中更改模板的方法,只需清除目录并在其中放下新格式的示例就可以了! :

 package myTest

 import io.gatling.core.Predef._
 import io.gatling.http.Predef._
 import scala.concurrent.duration._
 import scala.collection.JavaConversions._
 import java.io.File
 import java.io.FileNotFoundException


 class myTestSimulation extends Simulation {    


 val httpConf = http
    .baseUrl("*your_base_URL*")
.acceptHeader("application/json") // Here are the common headers
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections

val header = Map("Content-Type" -> """application/x-www-form-urlencoded""");
private var token = ""

val auth = scenario("Retrieve Token")
    .exec(
        http("POST OAuth Req")
        .post("*URL_for_Token*")
        .formParam("resource", "*your_resource_value*")
        .formParam("grant_type", "*your_grant_type*")
        .formParam("client_secret", "*your_client_secret_value*")
        .formParam("client_id", "*your_client_id_value*")
        .headers(header)
        .check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))  
        .exec{session => { token = session("access").as[String]
                         session}}       

object myTestObject {

    var headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")     
    val testData = Iterator.continually(
    new File("*pathway_to_file*") match {
      case d if d.isDirectory => d.listFiles.map(f => Map("filePath" -> f.getPath))
      case _ => throw new FileNotFoundException("Samples path must point to directory")
    }).flatten

    val myTestObjectMethod = feed(testData)
        .exec(session => session.set("access", token))            
        .exec(http("")              
            .post("*the_URL_to_send_to(don't_forget_that_base_URL_above_is_automatically_stuck_to_the_front_of_this!)*")
            .headers(headers_10.toMap)
            .body(RawFileBody("${filePath}")).asJson
            .check(status.is(200))
             )
}   

val scn = scenario("my_actual_load_test")
    .exec(myTestSimulation.myTestObject)
   setUp(
  auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(2 seconds), // waits 2 seconds as a margin to process token
    constantUsersPerSec(50) during (300 seconds) // fire 50 requests per second for 300 seconds
   ).protocols(httpConf))                             
   .assertions(global.responseTime.max.lt(500)) // set max acceptable response time
   .assertions(forAll.failedRequests.percent.lte(1)) // less than 1% of tests should fail
   .assertions(global.responseTime.mean.lte(100)) // set average response time
 }

我的意思是,当我从中删除敏感内容时,可能已经在某处打错了字,但希望这能满足您的需求。