Tryke
A Rust-based Python test runner with a Jest-style API.
Highlights
- Fast Rust-powered test discovery
- Concurrent tests by default
- Pretty, per-assertion diagnostics
- Soft assertions (like pytest-check)
- Native
asyncsupport — no plugin - Watch mode
- Changed mode (like pytest-picked)
- Client/server mode for fast editor integrations
- Fixtures with setup / teardown and typed
Depends()injection - Parametrized tests via
@test.cases - Grouping with
describe()blocks skip,skip_if,xfail, andtodomarkers- In-source testing
- Support for doctests
- Filtering and marks
- JSON, JUnit, Dot, and LLM reporters
Getting started
Write a test.
from tryke import Depends, describe, expect, fixture, test
class Users:
def __init__(self) -> None:
self._rows: dict[int, str] = {}
def create(self, name: str) -> int:
user_id = len(self._rows) + 1
self._rows[user_id] = name
return user_id
def get(self, user_id: int) -> str:
return self._rows[user_id]
# Fixtures combine setup + teardown in one function. `yield` splits them;
# `per="scope"` caches the value for every test in the lexical scope.
@fixture(per="scope")
def users():
db = Users()
yield db
db._rows.clear() # Teardown: runs once after the whole describe block.
with describe("users"):
# Dependencies are explicit and typed — `Depends(users)` resolves to
# the `Users` return type above, no magic name-matching required.
@test
def create_and_get(db: Users = Depends(users)):
user_id = db.create("alice")
# Assertions are soft by default: all three run even if one fails,
# so you get the full diagnostic in a single run.
expect(user_id).to_equal(1)
expect(db.get(user_id)).to_equal("alice")
expect(lambda: db.get(999)).to_raise(KeyError)
# Parametrize with `@test.cases` — each case is its own test ID,
# labels are arbitrary strings, kwargs are statically type-checked.
@test.cases(
test.case("lowercase", name="alice"),
test.case("with spaces", name="Alice Liddell"),
test.case("unicode", name="Алиса"),
)
def round_trips_names(name: str, db: Users = Depends(users)):
expect(db.get(db.create(name))).to_equal(name)
# Native async — no `pytest-asyncio` plugin needed.
@test
async def async_create(db: Users = Depends(users)):
expect(db.create("bob")).to_be_greater_than(0)
# Skip / xfail / todo markers ship in the box.
@test.xfail("reserved-name handling not implemented yet")
def rejects_reserved_names(db: Users = Depends(users)):
expect(lambda: db.create("admin")).to_raise(ValueError)
Run your tests — tryke watch for an always-on loop, tryke test --changed
for just what your working tree touched, or plain:
Installation
See the installation documentation.
Coming from pytest?
The migration guide has a side-by-side cheat sheet — but the fastest path is our copy-paste LLM prompt that walks an AI coding assistant through a phased, gated pytest → Tryke migration with discovery- and results-parity checks built in. Point it at a repo with a working pytest suite and it does the rest.