Skip to main content

An extremely fast Python test runner, written in Rust.

Project description

tezt

An extremely fast Python test runner, written in Rust.

CI License: MIT Status: alpha

"tez" means "fast" in Uzbek, and tezt sounds like "test". That's the whole pitch.


tezt runs Python tests. It is built around two ideas that make it quick: it discovers your tests by parsing them in Rust — no imports, no plugin loading — and it runs them on a pool of Python workers that start once and stay warm, so you pay interpreter startup a handful of times per run instead of once per test.

The difference shows up on anything non-trivial. On a generated suite of 10,000 tests across 500 files, listing the tests takes tezt about 20 ms where pytest takes about 10 s, and the full run takes ~0.2 s against pytest's ~13 s. Numbers and how to reproduce them are in Performance.

It aims to run ordinary pytest-style suites without changes — fixtures, parametrize, marks, conftest.py, async tests, the xunit setup_*/teardown_* hooks — and it reads both the attributes pytest's decorators produce and a small built-in import tezt API, so a project that doesn't have pytest installed can still run its tests under tezt.

Status

Alpha. The core runner is stable and the test suite runs on Linux, macOS, and Windows against CPython 3.9 through 3.14 (every push is checked across that matrix in CI). What isn't done yet — plugins, assertion rewriting, a few fixture scopes — is listed honestly in Compatibility and the Roadmap. If your suite leans on the pytest plugin ecosystem, keep using pytest; see the FAQ.

Quickstart

# test_math.py
import tezt


def test_addition():
    assert 1 + 1 == 2


@tezt.parametrize("n,squared", [(2, 4), (3, 9), (4, 16)])
def test_squares(n, squared):
    assert n * n == squared


def test_wrong_addition():
    total = 2 + 2
    assert total == 5

Point tezt at the directory (or just run it with no arguments in your project):

$ tezt
collected 5 tests in 6ms
python: .venv/bin/python3

FAILED test_math.py::test_wrong_addition
  assert failed: assert total == 5 | locals: total=4
  Traceback (most recent call last):
    File "test_math.py", line 15, in test_wrong_addition
      assert total == 5
  AssertionError

4 passed, 1 failed in 0.07s

tezt -v prints a line per test instead of a progress counter; tezt -q prints only the summary. The exit code follows pytest's convention — 0 all passed, 1 failures or errors, 5 nothing collected — so it drops into CI unchanged.

Using an existing suite

There are two ways to write tests, and tezt understands both.

The built-in API, which needs nothing installed:

import tezt

@tezt.fixture
def client():
    c = make_client()
    yield c
    c.close()

@tezt.parametrize("value", [1, 2, 3])
def test_positive(value, client):
    assert client.check(value) > 0

tezt provides fixture, parametrize, mark.skip/skipif/xfail, raises, skip, fail, and xfail, plus the builtin fixtures tmp_path, tmp_path_factory, and monkeypatch.

Plain pytest-style tests also work — tezt recognizes the markers @pytest.fixture and @pytest.mark.* leave on your functions. The one caveat is the obvious one: a file that says import pytest needs pytest installed to import, because tezt does not fake the pytest module. If you want a zero-dependency suite, use import tezt.

Compatibility

tezt is not a pytest reimplementation; it's a fast runner for the common subset. Here's where the line currently sits.

Works today Not yet
test_*.py / *_test.py, test_* functions, Test* classes The pytest plugin ecosystem (pytest-mock, -django, -cov, …)
Fixtures: function / module / session scope, yield teardown, conftest.py chains Assertion rewriting (pytest's per-operator introspection)
parametrize, including stacked decorators and ids= Class-scoped fixtures, async fixtures
skip / skipif / xfail / xpass Mark expressions (-m)
async def tests (run on a fresh event loop each) --lf / --ff, pdb, coverage
xunit hooks: setup_module, setup_class, setup_method, and teardowns Custom plugin hooks / pytest_*
Builtins: tmp_path, tmp_path_factory, monkeypatch
Reads @pytest.fixture / @pytest.mark.*, or the zero-dep import tezt API

Failures show the assertion's source line and the locals in scope, which covers most of what assertion rewriting gives you without the rewriting. Rich per-operator diffs are on the roadmap.

Performance

Median of 5 end-to-end runs (wall time around subprocess.run) on an 11-core Apple Silicon machine, against a generated suite of 10,000 tests in 500 files. The collection cache is turned off here so this is an honest parse-versus-import comparison; the harness lives in bench/ and the full table is regenerated into bench/RESULTS.md.

Phase tezt pytest pytest -n 8 (xdist)
Collect (--collect-only) 0.019 s 10.11 s
Full run 0.19 s 13.27 s 19.53 s
Cold start (1 test) 0.061 s 0.58 s

A couple of things worth saying plainly. The collection gap is large because pytest imports every module to discover tests while tezt only parses them. The full-run gap is mostly the per-test overhead: tezt spends about 0.02 ms of its own time per test, so on trivial tests the wall time is dominated by interpreter and dispatch costs that the warm worker pool amortizes away. And pytest -n 8 is slower than plain pytest on this suite — xdist's per-process distribution costs more than the tests themselves when each test is cheap, which is exactly the workload persistent workers are built for. On tests that actually do work, the gap narrows to whatever your test bodies cost, as it should.

The collection cache (on by default) takes this further on repeat runs: unchanged files skip parsing entirely and are reconstructed from a per-file entry keyed on size and mtime. That's another ~1.5× on this trivial suite and considerably more on real files where parsing isn't free.

CLI reference

Flag Description
-k EXPRESSION Run only tests whose id matches the expression (and / or / not, parentheses)
-x / --maxfail N Stop after the first failure, or after N failures/errors
-j N / --jobs N Number of worker processes (default: CPU count)
-v / -q One line per test / summary only
-s / --no-capture Don't capture test stdout/stderr
--collect-only List the tests that would run, then exit
--durations N Print the N slowest tests
--timeout SECONDS Kill and report any test that runs longer than this (off by default)
--json PATH Write a machine-readable JSON report
--python EXE Interpreter for the workers (also $TEZT_PYTHON); accepts a path or an X.Y version
--no-cache / --clear-cache Skip / delete the collection cache for this run
--color WHEN auto (default), always, or never

Configuration

tezt is configured by flags and a few environment variables; there is no config file yet.

  • TEZT_PYTHON — the interpreter to run workers with. When unset, tezt looks for an active virtualenv ($VIRTUAL_ENV), then $CONDA_PREFIX, then a project-local .venv (walking up to the project root), then python3/python on PATH. This is why tests run against the dependencies you actually installed rather than the system interpreter.
  • NO_COLOR — disables color, same as --color never (respects the standard).
  • TEZT_DEBUG=1 — logs internal detail to stderr, including any file whose static parse fell back to import-time discovery.
  • .tezt_cache/ — the per-project collection cache. It's safe to delete and is marked so backup tools skip it; add it to .gitignore (tezt writes one inside the directory too).

Installation

The one-line and pip installs need a published release. Until the first one lands, build from source (last option) — the release pipeline below is wired up and ready.

One line — downloads the right prebuilt binary for your platform and puts it on PATH:

# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/BilagoNet/tezt/main/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/BilagoNet/tezt/main/install.ps1 | iex

Set TEZT_INSTALL_DIR to pick the location or TEZT_VERSION to pin a tag.

With Python tooling — the binary ships inside a wheel, so no Rust toolchain is needed:

pip install tezt          # or:  uv tool install tezt

Direct download — grab a tezt-<target>.tar.gz / .zip from the Releases page and put the binary on your PATH.

From source — any platform with a recent stable Rust toolchain:

git clone https://github.com/BilagoNet/tezt
cd tezt
cargo build --release     # binary at ./target/release/tezt

How it works

Collection and execution are split between the two languages, each doing what it's good at.

Collection stays entirely in Rust: a parser walks each test file's syntax tree and builds the list of tests directly, so discovering a suite never starts a Python interpreter. Execution runs on a pool of worker processes that import each module once and then sit waiting for work, talking to the scheduler over a line-delimited JSON protocol on stdin/stdout. Because the workers are persistent, the cost of dispatching one more test is a JSON write and read — well under a millisecond — instead of a process launch.

flowchart LR
    A["Rust collector<br/>(parse, no imports)"] --> B[Scheduler]
    B -->|JSON lines| W1[worker 1]
    B -->|JSON lines| W2[worker 2]
    B -->|JSON lines| WN[worker N]
    W1 --> R[reporter]
    W2 --> R
    WN --> R

Interrupting a run is handled carefully, because a half-killed run that leaves Python processes behind is worse than no speedup at all. Each worker runs in its own process group on Unix and inside a kill-on-close Job Object on Windows, and a Ctrl-C handler tears the whole tree down — so an interrupted run never orphans a worker or a subprocess one of your tests spawned.

Roadmap

Roughly in the order it's likely to happen:

  • Prebuilt binaries + pip / uv tool distribution
  • Rich assertion diffs (operator-aware), beyond source line + locals
  • Mark expressions (-m)
  • Class-scoped and async fixtures
  • --lf / --ff (last-failed / failed-first)
  • A plugin API, and coverage integration

FAQ

Should I switch off pytest? For most projects, no — pytest is mature, extensible, and has an enormous plugin ecosystem that tezt doesn't try to replace. tezt is for the cases where the test loop itself is the bottleneck: large suites, tight edit-run cycles, CI minutes. If you live in those, the collection and startup savings are hard to give up; if you don't, pytest is the safer choice.

Will my existing suite run? If it sticks to fixtures, parametrize, marks, and conftest.py, there's a good chance it runs unchanged. If it depends on plugins or on pytest's assertion rewriting, it won't yet — the compatibility table is the honest boundary.

How is this different from karva, rtest, or maelstrom? They're all worth a look — this is a small wave of people asking what Rust can do for Python testing, the way uv and ruff did for packaging and linting. tezt's particular bet is the combination of static (no-import) collection, a warm worker pool with sub-millisecond dispatch, and running ordinary suites with no configuration.

Contributing

See CONTRIBUTING.md. The short version: cargo test runs the Rust unit and integration tests, python python/test_worker_protocol.py exercises the worker protocol directly, and bench/ has the benchmark harness. CI runs all of it across the OS and Python matrix.

Acknowledgements

tezt borrows pytest's design and ergonomics — its test layout, fixtures, and marks set the bar this tries to meet. The architecture takes after Astral's uv and ruff: a fast Rust core doing the heavy lifting for a Python workflow.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tezt-0.1.0.tar.gz (106.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

tezt-0.1.0-py3-none-win_amd64.whl (2.3 MB view details)

Uploaded Python 3Windows x86-64

tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.3 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (2.2 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

tezt-0.1.0-py3-none-macosx_11_0_arm64.whl (2.1 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl (2.2 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file tezt-0.1.0.tar.gz.

File metadata

  • Download URL: tezt-0.1.0.tar.gz
  • Upload date:
  • Size: 106.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tezt-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e33ee0634b01055713ae92074747a73b4d857f3b27b133cf992c66bf36f2897d
MD5 1d491c4eb46fe1556b9bd1a1c6edfa3b
BLAKE2b-256 8495c51dc308b53d8d0708a1a0e2a59566fafd32cca86c19982685f16e373458

See more details on using hashes here.

Provenance

The following attestation bundles were made for tezt-0.1.0.tar.gz:

Publisher: release.yml on BilagoNet/tezt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tezt-0.1.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: tezt-0.1.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 2.3 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tezt-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 32e5e8aa4c6d31e5b20f06e83e979318173d88abaca163396897e2b7ced79cd3
MD5 119baf90235b74720401a191d285360a
BLAKE2b-256 125a7e6266dcad0fc5570c8be59b1396059ff9ba0fbafe517211690cfdc428f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for tezt-0.1.0-py3-none-win_amd64.whl:

Publisher: release.yml on BilagoNet/tezt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a0c938645f947d93743a39ff3e79108438018388124ced9c23021da6bba3ca29
MD5 9c4f8bd7af8a76702534c8a88612834a
BLAKE2b-256 96d6786bc9feabac8b97c0a28149fe6a1ae296fd037b43e00d5242bf12fd9a37

See more details on using hashes here.

Provenance

The following attestation bundles were made for tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on BilagoNet/tezt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 6ba5201f8691601eed1b33b84ae373a9d8b00fd2174fe72c434f48bc88f241c2
MD5 cd900f5440eaaa947f022d25e1c891de
BLAKE2b-256 91ebbde9f80cfe94761809beb585880c69d0d2e9915c4e8eb5dc093f1ccb27bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on BilagoNet/tezt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tezt-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: tezt-0.1.0-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 2.1 MB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tezt-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 08ed79ebdb481f1e94736067f6b6b604065d73b8eb7acb6d7bc31d80dfa849b3
MD5 68f9df56178dc8fc95fd9a972910d9fc
BLAKE2b-256 b81283add96ed3ceabf73837698a33120bbf4bfe6b17129d63f181d461e368b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for tezt-0.1.0-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on BilagoNet/tezt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ae0d26773973daedaa7bbcefd4dc99b401b34af1e44182b9233c846f9802a60e
MD5 fc829728cff927670670f0f6e3262520
BLAKE2b-256 8634673fc36fb15ebe032e8813d609d161043e471ffcc047a50180a39648d0d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: release.yml on BilagoNet/tezt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page