Setting up your Grafana k6 performance testing suite: JavaScript tools, shared libraries, and more
Editor’s note: This blog post is the second in a series of posts about organizing your performance testing suite with Grafana k6. If you haven’t already, be sure to check out the first post in the series, which explores how to implement reusable test patterns and other best practices within your testing suite.
When we open sourced Grafana k6 in 2017, we had a simple yet ambitious plan: let’s build a performance testing tool that developers actually want to use and that helps engineering teams build more reliable software.
We’ve accomplished that goal, and have come a long way since those early days. We still, however, get a lot of questions about how to best organize a performance testing suite using Grafana k6.
In this post, we’ll look at some fundamentals of a large testing suite, including tools that can improve code quality, setting up repositories, sharing libraries, and more. These aspects are crucial for better maintenance, especially when multiple people collaborate in writing tests.
Whether you use open source Grafana k6 or Grafana Cloud k6 — our fully managed performance testing platform — these tools and practices will help you plan and ramp up faster.
JavaScript tools for code quality: Linters and Prettier
Even though k6 does not run in NodeJS, JavaScript and NodeJS offer a rich ecosystem of tools that we can use to enhance our testing experience.
ESLint and Prettier are two essential tools used in most JavaScript projects to analyze and update JavaScript code. ESLint — a tool known as a linter — helps identify and fix JavaScript errors, while Prettier ensures your test code is formatted consistently according to your desired style.
Both tools run on NodeJS and are usually installed via their npm packages. To configure project-related tools (and dependencies), managing k6 testing as an npm-based project can be an advantage.
Run npm init
on the root folder to create a package.json
file, then install any tools you want to use in your testing project. For instance, you might install the k6 TypeScript definition to enable Intellisense and support k6 auto-completion and documentation in your IDE.
I’ll forgo the ESLint and Prettier instructions as there are plenty of tutorials covering the topic. For reference, here are related posts by software quality expert Stuart Thomas and Grafana k6 Champion Grzegorz Piechnik, as well as the eslintrc.js used in k6 examples.
Note: For users new to k6, we want to emphasize that k6 does not run on NodeJS, and the k6 CLI should not be installed through an npm module. For more information, check out our k6 installation docs.
Multiple repos and central teams
A key decision in structuring our testing suite is whether to have one general suite or multiple testing suites distributed across projects. It’s important not to confuse this with the monorepo vs. multi-repo debate, which, although related, is a separate architectural decision.
Monorepos are generally easier to set up and manage. However, centralizing all tests in a separate repository might hinder the involvement of other teams, due to having less visibility and autonomy.
Our recommendation is to align with the structure of other testing projects in your organization. Commonly, testing projects are located alongside the codebase they are testing, so it is advisable to follow this practice. However, sometimes, test projects are managed as independent projects, and this approach is perfectly fine, too. Remember, there is no “one size fits all” when it comes to structuring testing projects.
The primary goal should be to ease the involvement of the teams responsible for building and testing the system. Adapting to fit the existing organizational and project structures, and placing the tests close to engineers, will facilitate adoption across teams and projects. Performance testing should never be a siloed project. The objective is to integrate it into the respective projects and teams’ processes, thereby building reliability into the different parts of the system.
Yet often, the challenges with cross-functional projects are a lack of ownership and the tendency to fall behind other priorities. Here is where a central group of Reliability and Quality leaders can play a crucial role. They lead the company-wide efforts and ensure that best practices are embedded across the engineering organization. Commonly, this central team is responsible for maintaining shared libraries and recommended practices.
To summarize, for large projects, we recommend:
- Using multiple test repositories aligned with your existing testing projects.
- Sharing common libraries maintained by a central team.
Bundling shared libraries
In this section, we’ll explore various options for building shared libraries with bundlers and importing them into testing projects. We suggest experimenting with an independent repository until your testing setup becomes stable enough to be implemented in other projects.
In large applications, we want to test the system end-to-end, but also test independently the distinct parts of the system and application scopes. Typically, each project has its own set of performance tests, sharing custom libraries across the various testing projects.
In k6, the simplest and preferred way to reuse JS libraries is to import them remotely from a URL. This could be a public web location like GitHub, a hosting server, or any CDN.
import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
export default function () {
randomItem();
}
If your JS library consists of several JavaScript modules split into distinct files, you probably want to bundle and release them as one or more public modules for easier consumption. For this, module bundlers like Rollup and Webpack are often used, performing two main tasks:
- Transpiling code between different JS formats, such as TypeScript to plain JavaScript.
- Bundling multiple files into one or several files for public release.
For reference, here are two examples that host public versions as GitHub release assets:
- k6-jslib-aws: Uses Webpack to transpile TypeScript to CommonJS modules.
- k6-rollup-example: Uses Rollup to combine various modules in
test-commons
into a single module file.
If you visit the GitHub release page for these projects, you’ll find the bundled public modules available as release assets:
Copy the link of the release asset and use this URL to import the remote module from your k6 tests:
import http from 'k6/http';
import { WorkloadConfig, sayHello } from 'https://github.com/grafana/k6-rollup-example/releases/download/v0.0.2/index.js';
export const options = {
stages: WorkloadConfig.smoke
};
export default function () {
console.log(sayHello());
http.get('https://pizza.grafana.fun/');
}
But what if you want to work with the library locally while developing your tests?
One option is to distribute the library as an npm-based project. The JS ecosystem is vast, so undoubtedly there are other approaches out there that I’d love to learn about.
By setting it up as an npm project, you could install the library locally via npm
into the node_modules
folder, and then import it as follows:
import { WorkloadConfig, sayHello } from 'test-commons';
Yay! But no so fast. Remember that k6 does not run on NodeJS, nor imports modules according to the NodeJS resolution algorithm. Therefore, the import
above will fail. However, here is where JS bundlers come to the rescue.
For instance, k6-rollup-example
uses @rollup/plugin-node-resolve to locate modules imported in the tests in the node_modules
directory. You just need an extra command to bundle a test with its dependencies:
npm run rollup -- --input samples/import-npm-test.js
In the dist
folder, Rollup
will create a new test file bundled with its dependencies. And now, you can run the bundled test as usual:
k6 run dist/import-npm-test.js
TypeScript support
TypeScript is great for larger projects, as type safety helps to prevent errors when using libraries and makes code more readable.
If you want to write k6 tests in TypeScript, you’ll need a bundler as shown before. This time, the bundler will transform TypeScript code into ES modules or plain old JavaScript (ES5.1) with CommonJS modules; both formats are compatible with k6.
For reference, take a look at the k6-template-typescript, which uses Webpack as the bundler. Alternatively, you can set up the @rollup/plugin-typescript in the previous Rollup example.
The good news is that TypeScript support is coming soon, which means you won’t need to use custom bundler setups. If you are a k6 extension user, you can already try out xk6-ts on GitHub, which is expected to be integrated into k6 in the near future.
Next steps and sharing with the community!
We hope you find these recommendations useful, and that you can apply them to create a better and more flexible testing suite. Implementing your own framework and libraries is key to adopt and maintain performance testing practices across the engineering organization. If you are looking for further guidance, be sure to check out our guide on automating performance testing. It provides guidelines to establish a repeatable performance testing process. And again, if you haven’t already, be sure to check out our earlier blog post about organizing your performance testing suite with k6.
A special thanks to Grafana k6 Champions Grzegorz Piechnik, Krzysztof Widera, Paul Maxwell-Walters, and Sahani Perera for helping to shape this post by sharing their expertise and experiences with us!
If you have your own recommendations or best practices that you’d like to share, please add them to our list of awesome k6 examples, as well as share them on Slack and in our Community Forum. Your insights are not only useful for fellow users, but also provide the k6 team vital information for considering future improvements.
And, as we always say: start simple and then iterate. Happy testing!