Writing tests¶
Testenix tests are ordinary typed functions. Discovery recognizes test_* names, while decorators
add explicit metadata without wrapping or changing the callable.
Plain functions¶
def test_total() -> None:
assert sum([2, 3, 5]) == 10
Both synchronous functions and coroutines are supported:
async def test_async_client() -> None:
response = await fetch_record(42)
assert response["id"] == 42
Parameter cases¶
Use case for named examples:
from testenix import case, cases, test
@test("discount rules")
@cases(
case("regular", amount=100, rate=0.0, expected=100),
case("member", amount=100, rate=0.1, expected=90),
)
def discount(amount: int, rate: float, expected: float) -> None:
assert amount * (1 - rate) == expected
Use keyword dimensions to create a Cartesian product:
@cases(
role=["admin", "editor"],
active=[True, False],
)
def test_permissions(role: str, active: bool) -> None:
...
Case values are rebuilt when the worker rediscovers the module, so they do not need to cross the process boundary through pickle. They must still be reproducible during module import.
Skip and expected failure¶
import sys
from testenix import skip, xfail
@skip("Windows-only behavior", when=sys.platform != "win32")
def test_windows_registry() -> None:
...
@xfail("tracked as issue #42")
def test_known_edge_case() -> None:
assert current_behavior() == desired_behavior()
An expected failure becomes XFAIL. If the test unexpectedly passes, the result is XPASS and
gates the run.
Retries and flakiness¶
Retries can be configured globally or on the command line:
$ testenix run --retries 2
Every attempt remains in the result model. FAIL -> PASS is finalized as FLAKY and returns a
gating exit code; Testenix never hides the initial failure.