Testenix¶
Testenix combines a dependency-free native runtime with a transparent bridge for running existing pytest suites unchanged.
Why Testenix¶
Testenix is deliberately built around a few strong guarantees:
Async is native. Coroutine tests and async-generator fixtures use the same model as synchronous code and do not require a plugin.
Parallelism is part of the runner. Adaptive worker selection, module affinity, optional safety-checked sharding, process isolation, and duration-aware scheduling are designed together.
Retries preserve evidence. A failed attempt followed by a pass is
FLAKY, never silently rewritten as a clean pass.Crashes cannot erase completed work. Workers stream results as tests finish, and unfinished tests receive explicit terminal outcomes.
Reports share one model. Console, JSON, JUnit, history, and the library API are derived from the same versioned result contracts.
A complete first test¶
Already have a pytest suite? Keep its fixtures, parametrization, markers, classes, configuration, and plugins:
$ python -m pip install "testenix[pytest]"
$ testenix pytest -q tests
Read the compatibility contract before migrating individual modules to the native engine.
To create a validated native copy without modifying the originals, use:
$ testenix migrate auto tests --dry-run
$ testenix migrate auto tests --check
$ testenix migrate auto tests --output tests_testenix
The migrator executes the source baseline and both serial and parallel native candidates in disposable project copies, compares their inventories and outcomes, and publishes only through an atomic no-overwrite rename. Read the safe migration contract.
from collections.abc import AsyncIterator
from testenix import case, cases, fixture, test
@fixture(scope="module")
async def multiplier() -> AsyncIterator[int]:
yield 2
@test("multiplication uses an async fixture", tags={"unit"})
@cases(
case(id="positive", value=3, expected=6),
case(id="zero", value=0, expected=0),
)
async def multiplication(multiplier: int, value: int, expected: int) -> None:
assert multiplier * value == expected
Run it locally:
$ python -m pip install testenix
$ testenix run tests
workers = "auto" adapts to the schedulable work instead of launching one process per CPU. Use
testenix tune (also available as testenix benchmark) for a measured project recommendation.
Import-heavy native suites can explicitly generate a source-hashed collection manifest, and large
independent modules can opt into conservative --shard-modules scheduling. See
parallel execution for both trust boundaries.
To evaluate unreleased source changes, install the current main branch with
python -m pip install "testenix @ git+https://github.com/polishdataengineer/testenix.git@main".
Performance evidence, with context¶
The checked-in development baseline measured Testenix 0.1.0, not the current release. Native
testenix run completed 100,000 generated no-op tests across 16 modules on one Apple M4 Pro and
CPython 3.11 machine in a median 8.04 seconds, compared with 25.33 seconds for pytest and 21.30
seconds for pytest-xdist. It used four workers, --no-history, and pytest-xdist’s default load
scheduler. These measurements do not apply to Testenix 0.3.0, a real project, the default-history
mode, or the delegated testenix pytest command.
Project maturity¶
Testenix is alpha software. The testenix pytest bridge preserves an existing suite by delegating
to real pytest, while testenix run is a distinct native engine rather than a drop-in pytest
reimplementation. Pytest still has a much broader plugin ecosystem, richer IDE integration, and
mature assertion rewriting. Choose the bridge for compatibility and the native engine when its
async model, built-in parallel execution, explicit failure semantics, or dependency-free core are
more important.