Beginner’s Guide to Load Testing with k6 — Part 3

7 minute read

Now that you know the basics and have defined your performance goals, it is now time to write your load-test script and run it using k6. Since k6 scripts can be written in JavaScript, you can easily write them if you have a minimum knowledge of this programming language.

k6 Test Life-cycle

There are three parts in each test script (actually there is more), which is depicted in the below screenshot:

Different parts of the k6 script
Different parts of the k6 script
  1. Imports: This is obviously where you can import k6 Script API, and other JavaScript modules (libraries) that you desire to make use of. These modules can be loaded using various methods: bundled NPM modules on your local machine (browserified, but browser APIs are not supported) and remote modules (from a URL, even CDNJS and GitHub). Hint: Make sure to make the libraries available to the dockerized version of k6 via mounted volumes, otherwise your script does not find the imports.
  2. Init code: This is the part of the script that is outside the exported “default” function. It is usually used to provide options to the whole test, how to run the test, how to distribute it on the cloud, etc. Obviously, it is used for initialization of the whole test itself.
  3. VU code: k6 supports a feature called virtual users. This means that you can use separate “smart” virtual users to test your system. The code in this section which is inside the exported “default” function, is ran over and over inside each VU and the aggregated results of all these VUs are processed and reported by k6. This is where you define your test scenario, which is thoroughly explained further in this article. There are other stages in the life-cycle of a k6 test, which includes setup and tear-down, which, clearly, are separate from the init and VU code.

How to Run k6?

There are various methods of running k6. Since the k6 is distributed in source code and binary, you can easily grab and install a binary version on your machine and run it. Supported operating systems include GNU/Linux, Microsoft Windows and Apple macOS. There is also a dockerized version that you can run on your docker setup.

No matter where you run the k6, it provides you with the same experience. Here’s the help of the k6 command:

k6 help
k6 help

The format of running k6 is like “k6 [command]”, which you can then pass your desired command and let it run by k6. Basic available commands, as of version 0.25.1, is as follows. Plus you can get help for each command with this combination:

k6 <command> --help
  • help: shows the above help, well, obviously.
  • pause: pause a running test.
  • resume: resume a paused test.
  • run: run a test with various flags, e.g.–*paused *to run a script in paused mode.
  • stats: shows statistics about the currently running or paused test entailing number of VUs.
  • status: show the status of current k6 instance, either running, paused, tainted and the number of VUs.
  • version: guess what?

These are more advanced commands that I will try to cover in the coming articles:

  • archive: creates a bundled tar file of your script along with all the dependencies, which you can later use with “k6 run”.
  • login: authenticates with k6 Cloud service and provides an authentication token to be used by k6.
  • cloud: runs your authenticated test on the k6 cloud service.
  • convert: browsers can log requests/responses as HAR (HTTP Archive) files, which you can then convert the HAR file using k6. k6 creates a script from the HAR file, which you can edit and run locally or on the cloud.
  • inspect: basically outputs the consolidated script options for that script or .tar bundle.
  • scale: scale a paused/running test with a new number of virtual users (VUs). It can do this either for local or for cloud execution. Each of these commands has a rich set of options/flags to pass in order to control the behavior of k6. I strongly recommend you to have a look at them, since sometimes you find gems inside.

Test Scenario & Interpretation of Results

Test scenario is an inseparable part of each test. If you want to test your API or website, you have to have a scenario and eventually turn it into a script, and then pass it to k6 to run.

Let’s have an example:

  1. A user checks of the API is up, hence checking the heart beat of our API and then tries to send further messages.
  2. Then they try to create a token to be able to access other parts of our API or the private areas.
  3. Finally they try to access an endpoint of our API.
[heart-beat]->[generate-token]->[get-list-of-users]

You also want to add some more criteria and thresholds, to be able to discern more meaningful results from the k6. Although some information, like threshold is new in this article, I’ll try to explain them completely in the upcoming article. You want this scenario to be tested by 100 individual users, starting with 10 users, ramping up to 100 and the gradually down to 0, and to keep the response time of all request below 500ms.

The script would be something like this:

import { check, group, sleep } from 'k6';
import http from 'k6/http';

export let options = {
  max_vus: 100,
  vus: 100,
  stages: [
    { duration: "30s", target: 10 },
    { duration: "4m", target: 100 },
    { duration: "30s", target: 0 }
  ],
  thresholds: {
    "RTT": ["avg<500"]
  }
}

export default function() {
  group('v1 API testing', function() {
    group('heart-beat', function() {
      let res = http.get("https://httpbin.org/get");
      check(res, { "status is 200": (r) => r.status === 200 });
    });

    group('login', function() {
      let res = http.get("https://httpbin.org/bearer", {
        headers: { "Authorization": "Bearer da39a3ee5e6b4b0d3255bfef95601890afd80709" }
      });
      check(res, {
        "status is 200": (r) => r.status === 200,
        "is authenticated": (r) => r.json()["authenticated"] === true
      });
    });

    group('access an endpoint', function() {
      let res = http.get("https://httpbin.org/base64/azYgaXMgYXdlc29tZSE=");
      check(res, {
        "status is 200": (r) => r.status === 200,
        "k6 is awesome!": (r) => r.body === "k6 is awesome!"
      });
    });
  });
  sleep(1);
}

I have used httpbin.org as a kitchen-sink for testing k6 with this script. The results of running test for five minutes is as follows:

Output of k6 scenario script
Output of k6 scenario script

The output start with the logo of the k6 being printed to the screen. After that comes the method of execution, being “local” in my case. The output is redirected to the terminal and the name of the script is “test.js”.

Duration and iterations are empty, since they are automatically calculated from the stages. The number of (max) VUs are 100 and the progress bar shows that it is done in five minutes.

“v1 API Testing” and other group names are followed by a white full block sign “█” and the checks are all ticked and green, indicating they are passed in all cases.

All the checks in the tests are passed, specifically 56213 checks in 33741 requests. The amount of data sent and received, vus, iterations and some other metrics are also shown.

The very important part is the metrics that start with http. They signify the average, minimum, maximum, p(90) and p(95) of the amount of time each request or group of request has taken to complete.

Since we have defined the average threshold to be 500ms. Since the average time of all requests hasn’t taken that long, the test is passed.

The results shown on screen is the aggregated results on all the tests. If you want to be able to use all the generated data, and not only the aggregated ones, you should use the -o flag to send the output to either a files, a software or the cloud service.

The raw results can be written to a JSON file using JSON plugin. There are other plugins that push the metrics to InfluxDB, Apache Kafka, StatsD or Datadog. The last (and probably the best) option would be to use the Load Impact plugin that streams your test results to the Load Impact cloud platform.

In this article I tried my best to show you how to write a pretty simple test scenario script and how to run to the load test using k6.

In the next article I’ll try to turn concepts I’ve talked about on the second article — Performance Goals and k6 Metrics — into features supported by k6 that you can use to have a successful load or acceptance testing.

As always, I do welcome your feedback. Please don’t hesitate to write your comments, questions and feedback below.

Updated:

Comments