Sequential execution of describe blocks

Hi Team,

I’m pretty much new to k6. Since I have a background of BDD and I cam across the K6 supports BDD format (k6chaijs). I have started using the same.

I’m stuck up in a situation where I want to pass the data between the describe blocks and want to execute the describe blocks seqentially.

How could I let the data flow between the describe blocks in sequential manner?

Example:

let userVoucherMap = {}
export function setup() --> which creates the user and returns 'data'

export default function (data) {
    describe ('Load test voucher creation api', () => {
         data --> The data is accessible here without any issues

         Here I first load test voucher creation api by associating the users with the voucher.

         The vouchers that are created here are then used in next describe for redemption.

         That is: I create voucher for the users and store the user and voucher mapping in a global 
          variable: 'userVoucherMap ' 
   })

  describe ('Load test voucher redemption api', () => {
        The userVoucherMap is available here.

        But the problem is that both the describes are executed parallelly. So I do not get the entire 
        mapping. I want to wait until above describe is finished and then execute the current one.
})
}

How could I run the describe blocks sequentially ? Your help and inputs would be highly appreciated.

P.S. I did some research and came across material where it was illustrated/suggested to use scenarios and then use one scenarios’ startTime + gracefulStop to start the other scenario. But I do not want to use scenarios

Thanks,
Zaid

Hi @zaidmomin1

Could you please post the full (anonymized and sanitized) options you either set in your k6 script or pass through the command line?

It’s hard to judge simply from your example why your script doesn’t do what you would wish it to do. But I got a few hints from your description :smile_cat: (Thanks for the detailed example script, by the way).

To execute your script, k6 relies on a concept we call VUs. From the user perspective, each VU will execute the user default function (or named function in scenarios) in parallel. Each VU taken separately executes the function in a linear, synchronous blocking way. This means that from the viewpoint of a single VU, the two described statements you mentioned are blocking are, in fact, executed after one another.

Now, I imagine, based on your description of your issue, that you might be running multiple VUs, and that those VUs might be filling and updating the userVoucherMap in parallel. You expect the second describe statement to start when that first process is entirely done.

If that’s the case, I’m afraid that scenarios might be the only way to go, as you have discovered by yourself. k6 is only able to sequence scenarios based on a time parameter at the moment. Still, we’re aware that doing it in a more declarative way (first run scenario A, then B, etc) is a desired feature, and although we haven’t prioritized it yet, it’s on our radar.

It would be hard to figure out if that’s possible without more details, but maybe filling the userVouchMap directly in the setup function might be an option. Indeed, the setup function is executed only once during the whole load test and before any VUs start.

Otherwise, it might be worth looking into using the k6/experimental/redis module if you are familiar with Redis, which might allow you to implement some level of “cleverness” in deciding when you’re done filling the userVoucherMap, and ready to proceed with the other step?

Regardless, I hope this was useful.
Let me know if you have any more questions :bowing_man:

2 Likes

Hi @oleiade Thanks much for the quick response :slight_smile:

Thanks for your valuable insights and inputs.

You expect the second describe statement to start when that first process is entirely done.

Yes this is what exactly I wanted but as you said this a feature yet to be implemented, so gotta wait until then.

but maybe filling the userVouchMap directly in the setup function might be an option.

Yes I could that. But if I put voucher creation in setup then, I won’t be actually load testing the voucher creation. What I intend to do is

  1. Load test the voucher creation and store the created vouchers in an object.
  2. Use the created vouchers for redemption in a separate describe block rather then creating new vouchers and redeeming it in the same the describe.

Given the current situation and sequential executing of describes is yet to be implemented.

I have separated my tests.

  1. Load test voucher creation api
describe('Voucher creation', () => {
      Just load test voucher creation
})
  1. Create vouchers and redeem it
describe('Voucher creation', () => {
      Create vouchers
      Redeem them
})

You might say second describe is the superset of the first describe as it’s load testing both creation and redemption which is valid. But as a requirement we also want to load test just the creation without redemption.

I’ll also have a look at k6/experimental/redis in course of time.

Appreciate your help and support.

Thanks,
Zaid

Hey @zaidmomin1

Happy to help :smile:

Thanks a lot for the clarifications :bowing_man:

From what you wrote, it sounds like, in the absence of support for sequential scenarios, you might consider decoupling testing the user voucher creation from the redeem step. My thinking there is that based on your description of what you’re trying to achieve, you could actually put scenarios to good use, by having:

  • One scenario that consists in creating vouchers only. Without storing them for further share, just testing the performance of that step.
  • A setup function that reproduces the same behavior, but does fill a shared object with the data. Its execution won’t be measured.
  • A second scenario, which redeems the vouchers from the object created in the setup phase?

I’ve put together a small script illustrating my thinking, you end up running the create vouchers step twice, separately, but I believe it might get you one step further towards your objective?

export let options = {
  scenarios: {
    create_voucher: {
      // name of the executor to use
      executor: "shared-iterations",

      // executor-specific configuration
      exec: "createVoucher",
      vus: 10,
      iterations: 200,
    },

    redeem_voucher: {
      // name of the executor to use
      executor: "shared-iterations",

      // executor-specific configuration
      exec: "redeemVouchers",
      vus: 10,
      iterations: 200,
    },
  },
};

export function setup() {
  console.log("setup");

  return {
    vouchers: createVoucher(),
  };
}

export function createVoucher() {
  console.log("creatingVoucher");

  const userVouchers = {};

  // 1. create user vouchers
  // for (let i = 0; i < 100; i++) {
  //   http.post("https://myservice.com/vouchers/user_id", {});
  //
  //  // 2. accumulate vouchers
  //  Object.assign(userVouchers[user_id], response.json());
  // }

  return userVouchers;
}

export function redeemVouchers() {
  console.log("redeemingVoucher");

  // 3. redeem vouchers
  // for (const [key, value] of Object.entries(data.userVouchers)) {
  //   http.get("https://myservice.com/vouchers/user_id");
  // }
}

Is that helpful?

2 Likes

Hi @oleiade

Indeed these are valuable inputs and do make my life easier.

Thank for sharing in depth details, helping me out with the script and giving me the direction to move forward :slight_smile:

Your help and support is highly appreciated.

Thanks,
Zaid

1 Like

you’re very welcome, happy I could help :bowing_man:

2 Likes