k6

A modern load testing tool, using Go and JavaScript

"like unit testing, for performance"

k6 is a modern load testing tool, building on Load Impact's years of experience. It provides a clean, approachable JavaScript scripting API, distributed and cloud execution, and orchestration via a REST API.

Get Started    Guides

Test life cycle

There are four distinct life cycle stages to a k6 test that can be controlled by you, the user. They are the "init", "setup", "vu" and "teardown" stages. We also refer to it as "init code", "vu code" etc. in the documentation.

// 1. init code

export function setup() {
    // 2. setup code
}

export default function(data) {
    // 3. vu code
}

export function teardown(data) {
    // 4. teardown code
}

Init and VU stages

Scripts must contain, at the very least, a default function - this defines the entry point for your VUs, similar to the main() function in many other languages:

export default function() {
    // do things here...
}

"Why not just run my script normally, from top to bottom", you might ask - the answer is: we do, but code inside and outside your default function can do different things.

Code inside default is called "VU code", and is run over and over for as long as the test is running. Code outside of it is called "init code", and is run only once per VU.

VU code can make HTTP requests, emit metrics, and generally do everything you'd expect a load test to do - with a few important exceptions: you can't load anything from your local filesystem, or import any other modules. This all has to be done from init code.

There are two reasons for this. The first is, of course: performance.

If you read a file from disk on every single script iteration, it'd be needlessly slow; even if you cache the contents of the file and any imported modules, it'd mean the first run of the script would be much slower than all the others. Worse yet, if you have a script that imports or loads things based on things that can only be known at runtime, you'd get slow iterations thrown in every time you load something new.

But there's another, more interesting reason. By forcing all imports and file reads into the init context, we make an important design goal possible; we want to support three different execution modes without the need for you to modify your scripts; local, cloud and clustered execution. In the case of cloud and clustered execution we know which files will be needed, so we distribute only those files. We know which modules will be imported, so we can bundle them up from the get-go. And, tying into the performance point above, the other nodes don't even need writable filesystems - everything can be kept in-memory.

Setup and teardown stages

Beyond the required init and VU stages, which is code run for each VU, k6 also supports test wide setup and teardown stages, like many other testing frameworks and tools. The setup and teardown functions, like the default function, needs to be exported functions. But unlike the default function setup and teardown are only called once for a test. setup is called at the beginning of the test, after the init stage but before the VU stage (default function), and teardown is called at the end of a test, after the VU stage (default function).

Again, let's have a look at the basic structure of a k6 test:

// 1. init code

export function setup() {
    // 2. setup code
}

export default function(data) {
    // 3. vu code
}

export function teardown(data) {
    // 4. teardown code
}

You might have noticed the function signature of the default function and teardown function takes an argument, which we here refer to as data. This data will be whatever is returned in the setup function, so a mechanism for passing data from the setup stage to the subsequent VU and teardown stages in a way that, again, is compatible with our goal of supporting local, cloud and clustered execution modes without requiring script changes when switching between them (it might or might not be the same node that runs the setup and teardown stages in cloud or clustered execution mode). To support all of those modes, only data (i.e. JSON) can be passed between setup() and the other stages, any passed functions will be stripped.

Here's an example of doing just that, passing some data from setup to VU and teardown stages:

export function setup() {
    return {v: 1};
}

export default function(data) {
    console.log(JSON.stringify(data));
}

export function teardown(data) {
    if (data.v != 1) {
        throw new Error("incorrect data: " + JSON.stringify(data));
    }
}

A big difference between the init stage and setup/teardown stages is that you have the full k6 API available in the latter, you can for example make HTTP requests in the setup and teardown stages:

export function setup() {
    let res = http.get("https://httpbin.org/get");
    return {data: res.json()};
}

export function teardown(data) {
    console.log(JSON.stringify(data));
}

export default function(data) {
    console.log(JSON.stringify(data));
}

Skip setup and teardown execution

The are two CLI options that can be used to skip the execution of setup and teardown stages.

k6 run --no-setup --no-teardown ...