Skip to content

Test Runners

A test runner discovers test files, executes the cases, collects results and reports pass/fail. It is the engine underneath the assertions: it decides what runs, in what order, how failures are surfaced, and how exit codes drive CI.

Runners split into two broad styles:

Code-driven runners Keyword/table-driven runners
┌───────────────────────┐ ┌───────────────────────────┐
│ test files → functions│ │ keyword libraries │
│ ↓ run in-process │ │ ↓ data tables .robot │
│ assertions + fixtures │ │ steps read like prose │
└──────────┬────────────┘ └────────────┬──────────────┘
▼ ▼
exit code → CI exit code + HTML report → CI

Robot Framework is the flagship of the second style: tests are written as human-readable keywords in tabular .robot files, so testers and domain experts can read and author suites without programming. It is widely used for system and acceptance testing and ships its own runner command, robot.

Use a runner when

  • You need consistent discovery, isolation and reporting for a suite of tests
  • You want a single exit code that gates your CI pipeline
  • You need parallelism, retries, tagging or sharding at scale
  • Non-programmers must read or write the tests (→ Robot Framework, keyword-driven)

Avoid or reduce it when

  • The project has a handful of one-off checks you run by hand
  • The runner would become a thick framework layer over simple scripts
  • You only need a quick assertion and already have a shell/script workflow
Tool Ecosystem Notes
Vitest JS/TS Vite-native, Jest-compatible API, fast watch mode
Jest JS/TS Batteries included: runner, assertions, mocks, coverage
Mocha JS/TS Flexible runner; pair with Chai/Sinon
pytest Python Fixtures, parametrisation, rich plugin ecosystem
go test Go Built in; table-driven tests are idiomatic
JUnit 5 Java/JVM The JVM standard, with a platform/engine split
TestNG Java/JVM Suites, groups, parallel and data-driven execution
xUnit .NET Modern, [Fact]/[Theory]
NUnit .NET Mature, data-driven [TestCase]
Robot Framework Python/JVM Keyword-driven runner; tabular .robot suites, robot CLI
Terminal window
npm install -D vitest
npx vitest # watch mode; `npx vitest run` for CI
import { describe, it, expect } from 'vitest';
describe('parser', () => {
it('reads a key=value line', () => {
expect(parse('a=1')).toEqual({ a: '1' });
});
});
  • Keep the runner thin. A runner should orchestrate, not contain business setup; push reusable logic into libraries and fixtures.
  • Pin the runner and its plugins so a CI upgrade does not change behaviour silently.
  • Fail fast, but report everything. Use --bail only when flakiness is under control.
  • Use tags/groups for selective runs instead of commenting tests in and out.
  • For keyword-driven suites, keep keywords reusable and avoid putting assertion logic in ad-hoc strings scattered across test cases.
  • Do not mix styles in one suite without reason: pick code-driven or keyword-driven per layer of the pyramid.

Self-assessment quiz

Pick one answer per question, then press Check answers.

  1. What is the primary job of a test runner?
  2. Which runner is keyword-driven and uses tabular .robot files?
  3. Why does the runner exit code matter in CI?
  4. Which pairing is the standard runner for Go?
  5. When is a heavyweight runner a poor choice?