Как использовать один токен OAuth2.0 для нескольких виртуальных пользователей в нагрузочном тесте Gatling

Мне нужно загрузить и протестировать API, для которого требуется токен OAuth2.0 через Gatling (в этом я совсем новичок!), но хотел бы, чтобы каждый виртуальный пользователь использовал один и тот же токен. Я получаю токен в порядке (я думаю) и помещаю его в переменную с именем «доступ», но я продолжаю получать «атрибут с именем «доступ» не определен», когда начинается сам тест.

Мой поиск токена выглядит следующим образом (вместе с 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"))) 

Затем я попытался настроить нагрузочный тест как (Примечание: я изначально поставил «Карта», а не изменяемый вариант, но где-то читал, что значение по умолчанию было неизменным, и задался вопросом, почему заголовок не может обновляться):

 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 

Я достиг конца своей привязи с этим. Я предполагаю, что это может быть как-то связано с областями, и, возможно, переменная не переносится в сценарий нагрузочного теста, но я видел примеры в другом месте, которые, похоже, рекомендуют именно эту настройку, поэтому я не знаю являются ли эти другие примеры частично полными или что.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
3 864
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Сеансы для каждого пользователя, и никакие данные сеанса не передаются пользователям. Таким образом, хотя у вас есть 1 пользователь, запускающий ваш сценарий «auth» и сохраняющий токен, это два разных пользователя, которые запускают «scn», и у них нет доступа к значениям сеанса пользователя auth.

Это не рекомендуется, но вы можете решить эту проблему, вставив токен аутентификации в обычную переменную scala, установив this в сценарии аутентификации и прочитав его в основном сценарии - вам просто нужно убедиться, что аутентификация всегда завершается, прежде чем вводить какие-либо другие пользователи.

var token: String = ""

затем в сценарии авторизации сделайте шаг в конце, например

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

затем в начале сценария scn есть шаг для установки переменной сеанса

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

Я использовал этот шаблон в прошлом, и он работает, но я уверен, что есть более приятные способы сделать это.

Спасибо за ваш ответ, Джеймс, но я все еще не могу заставить его работать. Моя проблема заключается в бите 'token = session.get("access").as[String]. Он не распознает «получить». Затем я попробовал '.exec(session => { token = session("access")})', что по понятным причинам дало несоответствие типа, поскольку токен был строкой, и он возвращал тип SessionAttribute. Затем я снова прочитал «.as[String]» до конца, и теперь я получаю другое несоответствие типов — теперь он говорит, что нашел тип «единица», но ожидал «проверки»?!?!

AmFearLiathMor 08.04.2019 10:19

Привет Джеймс - нашел проблему. 'get' больше не является частью сеансового API в более поздних версиях Gatling, которую я использую (плюс, я думаю, что это должны быть фигурные скобки, а не обычные скобки вокруг сеанса!). Если вы хотите отредактировать свой ответ, упомянув, что вам может понадобиться .exec{session => { token = session("access").as[String] session}} , если вы используете более позднюю версию Gatling, я отмечу это как правильный ответ. Спасибо за вашу помощь!

AmFearLiathMor 08.04.2019 17:08

Я также пробую этот сценарий, но получаю сообщение об ошибке при попытке добавить .exec{session => {token = session("access").as[String] session}} в начале сценария scn.

       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)) 

но ни один из них не работал. Не могли бы вы предложить мне, где я должен на самом деле поместить этот код.

Привет Тарун - см. ответ ниже. Надеюсь, это покроет то, что вы искали.

AmFearLiathMor 12.06.2019 15:32

@Тарун,

Когда я это сделал, у меня в сценарии был '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))
             )

Как упоминалось в комментариях в предыдущем обсуждении, это произошло потому, что я использовал более позднюю версию гатлинга, а метод «получить» больше не был частью 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
 }

Я имею в виду, что я, вероятно, сделал опечатку где-то в строке, когда удалял из нее конфиденциальные вещи, но, надеюсь, это сделает то, что вам нужно.

Сегодня я реализовал этот сценарий для своего проекта. Пожалуйста, посмотрите код ниже, и он будет работать и для вас.

Примечание. Я написал этот код с необходимыми параметрами моего 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)

     }`

Другие вопросы по теме