No results for

Powered byAlgolia

Reuse and re-run tests

In the previous tutorials, you designed k6 scripts to assert performance and make comparing results easy.

In this tutorial, learn how to:

  • Modularize test scripts into reusable components
  • Dynamically configure scripts with environment variables

Example script

For fun, let's combine the scripts from the previous tutorials. Use the logic of the multiple-flows.js test with the thresholds and scenario of the api-test.js (feel free to add more checks, requests, groups, and so on). Take note of the features of this script:

  • The default function has two groups, Contacts flow, and Coinflip game
  • The options object has two properties, thresholds and scenarios

In the following sections, learn how to split these components into separate files, and combine them dynamically at run time.

whole-tutorial.js
import http from 'k6/http';
import { group, sleep } from 'k6';
import { Trend } from 'k6/metrics';
//define configuration
export const options = {
scenarios: {
//arbitrary name of scenario:
breaking: {
executor: 'ramping-vus',
stages: [
{ duration: '10s', target: 20 },
{ duration: '50s', target: 20 },
{ duration: '50s', target: 40 },
{ duration: '50s', target: 60 },
{ duration: '50s', target: 80 },
{ duration: '50s', target: 100 },
{ duration: '50s', target: 120 },
{ duration: '50s', target: 140 },
//....
],
},
},
//define thresholds
thresholds: {
http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
},
};
//set baseURL
const baseUrl = 'https://test.k6.io';
// Create custom trends
const contactsLatency = new Trend('contacts_duration');
const coinflipLatency = new Trend('coinflip_duration');
export default function () {
// Put visits to contact page in one group
let res;
group('Contacts flow', function () {
// save response as variable
res = http.get(`${baseUrl}/contacts.php`);
// add duration property to metric
contactsLatency.add(res.timings.duration);
sleep(1);
res = http.get(`${baseUrl}/`);
// add duration property to metric
contactsLatency.add(res.timings.duration);
sleep(1);
});
// Coinflip players in another group
group('Coinflip game', function () {
// save response as variable
let res = http.get(`${baseUrl}/flip_coin.php?bet=heads`);
// add duration property to metric
coinflipLatency.add(res.timings.duration);
sleep(1);
res = http.get(`${baseUrl}/flip_coin.php?bet=tails`);
// add duration property to metric
coinflipLatency.add(res.timings.duration);
sleep(1);
});
}

Modularize logic

With modules, you can use logic and variables from other files. Use modules to extract the functions to their own files.

To do so, follow these steps:

  1. Copy the previous script (whole-tutorial.js) and save it as main.js.

  2. Extract the Contacts flow group function from main.js script file and paste it into a new file called contacts.js

    contacts.js
    1export function contacts() {
    2 group('Contacts flow', function () {
    3 // save response as variable
    4 let res = http.get(`${baseUrl}/contacts.php`);
    5 // add duration property to metric
    6 contactsLatency.add(res.timings.duration);
    7 sleep(1);
    8
    9 res = http.get(`${baseUrl}/`);
    10 // add duration property to metric
    11 contactsLatency.add(res.timings.duration);
    12 sleep(1);
    13 });
    14 }

    As is, this script won't work, since it has undeclared functions and variables.

  3. Add the necessary imports and variables. This script uses the group, sleep, and http functions or libraries. It also has a custom metric. Since this metric is specific to the group, you can add it contacts.js.

  4. Finally, pass baseUrl as a parameter of the contacts function.

    contacts.js
    1import http from "k6/http";
    2import { Trend } from "k6/metrics";
    3import { group, sleep } from "k6";
    4
    5const contactsLatency = new Trend("contact_duration");
    6
    7export function contacts(baseUrl) {
    8 group('Contacts flow', function () {
    9 // save response as variable
    10 let res = http.get(`${baseUrl}/contacts.php`);
    11 // add duration property to metric
    12 contactsLatency.add(res.timings.duration);
    13 sleep(1);
    14
    15 res = http.get(`${baseUrl}/`);
    16 // add duration property to metric
    17 contactsLatency.add(res.timings.duration);
    18 sleep(1);
    19 });
    20}
  5. Repeat the process with the coinflip group in a file called coinflip.js. Use the tabs to see the final three files should (options moved to the bottom of main.js for better readability).

    main.js
    contacts.js
    coinflip.js
    import { contacts } from "./contacts.js";
    import { coinflip } from "./coinflip.js";
    const baseUrl = "https://test.k6.io";
    export default function () {
    // Put visits to contact page in one group
    contacts(baseUrl);
    // Coinflip players in another group
    coinflip(baseUrl);
    }
    //define configuration
    export const options = {
    scenarios: {
    //arbitrary name of scenario:
    breaking: {
    executor: 'ramping-vus',
    stages: [
    { duration: '10s', target: 20 },
    { duration: '50s', target: 20 },
    { duration: '50s', target: 40 },
    { duration: '50s', target: 60 },
    { duration: '50s', target: 80 },
    { duration: '50s', target: 100 },
    { duration: '50s', target: 120 },
    { duration: '50s', target: 140 },
    //....
    ],
    },
    },
    //define thresholds
    thresholds: {
    http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
    http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
    },
    };

Run the test:

# setting the workload to 10 iterations to limit run time
k6 run main.js --iterations 10

The results should be very similar to running the script in a combined file, since these are the same test.

Modularize workload

Now that the iteration code is totally modularized, you might modularize your options, too.

The following example creates a module config.js to export the threshold and workload settings.

main.js
config.js
import { coinflip } from "./coinflip.js";
import { contacts } from "./contacts.js";
import { thresholdsSettings, breakingWorkload } from "./config.js";
export const options = {
scenarios: { breaking: breakingWorkload },
thresholds: thresholdsSettings
};
const baseUrl = "https://test.k6.io";
export default function () {
contacts(baseUrl);
coinflip(baseUrl);
}

Notice the length of this final script and compare it with the script at the beginning of this page. Though the final execution is the same, it's half the size and more readable.

Besides shortness, this modularity lets you compose scripts from many parts, or dynamically configure scripts at run time.

Mix and match logic

With modularized configuration and logic, you can mix and match logic. An easy way to configure this is through environment variables.

Change main.js and config.js so that it:

  • By default runs a smoke test with 5 iterations
  • With the right environment variable value, runs a breaking test

To do this, follow these steps:

  1. Add the workload settings for configuring the smoke test to config.js:

    config.js
    export const smokeWorkload = {
    executor: 'shared-iterations',
    iterations: 5,
    vus:1
    }
    export const thresholdsSettings = {
    http_req_failed: [{ threshold: "rate<0.01", abortOnFail: true }],
    http_req_duration: ["p(99)<1000"],
    }
    export const breakingWorkload = {
    executor: 'ramping-vus',
    stages: [
    { duration: '10s', target: 20 },
    { duration: '50s', target: 20 },
    { duration: '50s', target: 40 },
    { duration: '50s', target: 60 },
    { duration: '50s', target: 80 },
    { duration: '50s', target: 100 },
    { duration: '50s', target: 120 },
    { duration: '50s', target: 140 },
    //....
    ],
    }
  2. Edit main.js to choose the workload settings depending on the WORKLOAD environment variable. For example:

    main.js
    import { coinflip } from "./coinflip.js";
    import { contacts } from "./contacts.js";
    import { thresholdsSettings, breakingWorkload, smokeWorkload } from "./config.js";
    export const options = {
    scenarios: {
    my_scenario: __ENV.WORKLOAD === 'breaking' ? breakingWorkload : smokeWorkload
    },
    thresholds: thresholdsSettings
    };
    const baseUrl = "https://test.k6.io";
    export default function () {
    contacts(baseUrl);
    coinflip(baseUrl);
    }
  3. Run the script with and without the -e flag.

    • What happens when you run k6 run main.js?
    • What about k6 run main.js -e WORKLOAD=breaking?

This was a simple example to showcase how you can modularize a test. As your test suite grows and more people are involved in performance testing, your modularization strategy becomes essential to building and maintaining an efficient testing suite.

Next steps

Now you've seen examples to write tests, assert for performance, filter results, and modularize scripts. Notice how the tests progress in complexity: from single endpoints to holistic tests, from small to large loads, and from single tests to reusable modules. These progressions are typical in testing, with the next step being to automate. It might be impractical to automate a tutorial, but if you are interested, read the Automated performance testing guide.

More likely, you want to learn more about k6. The k6-learn repository has more details to practice. Or, you can read and explore the load testing guides and try to build out your testing strategy.

Happy testing!