Skip to main content

TestRex

CI Coverage Security PyPI License

A pytest plugin that holds a test suite to one layout and one standard of test quality

My repos share one way of laying out and writing tests. This plugin carries the rules, so a repo gets them, and every fix to them, through one dependency bump instead of a copied meta suite.

It does three things:

  • The meta suite. Around thirty rules that read every test file and fail on tests that cannot fail, files in the wrong place, duplicated definitions and helpers, undeclared programs, broken override signatures and pylint findings. There are no exemption lists: a test that trips a rule is fixed.
  • Required programs. requires_binary (and any marker a repo adds) skips a test while collecting, before any of its fixtures start, when a program it needs is missing.
  • Hermetic runs. Outbound network is closed to tests unless they are marked allow_network, and the programs a repo seals (sudo, ssh, a password manager) are refused however a test starts them.

Setting up a repo

Install it with the test dependencies. With a hash-locked requirements-dev.in:

pytest-testrex

Then opt in with a [tool.testrex] table in pyproject.toml. Without one the plugin does nothing, so having it installed never changes another project's run:

[tool.testrex]
suites = ["unit", "integration"]

and run the meta suite by subclassing it in a test module under a meta directory, by default Tests/unit/meta/:

# Tests/unit/meta/test_testrex.py
from pytest_testrex import suite


class TestRex(suite.TestRex):
    """The suite's own rules, from pytest-testrex."""

The plugin parametrizes each rule over the files it reads, so a failure names the file:

FAILED Tests/unit/meta/test_testrex.py::TestRex::test_every_test_checks_something[unit/pkg/test_mod.py]
    asserts nothing: ['test_parse (line 12)']

Layout

Each suite mirrors the repository: a test lives at the path of the code it covers.

pkg/strings.py                → Tests/unit/pkg/test_strings.py
pkg/net/fetch.py              → Tests/unit/pkg/net/test_fetch.py
scripts/setup.sh              → Tests/unit/scripts/test_setup.py
.githooks/pre-commit          → Tests/unit/githooks/test_pre_commit.py
pkg/net/fetch.py (real HTTP)  → Tests/integration/pkg/net/test_fetch.py

What decides the suite is whether a test needs a program outside the process, not whether it touches real files. A test that works in tmp_path with the code and its Python dependencies alone is a unit test; one that runs git, docker or 7zz goes in integration/ and declares it.

A test file that outgrows max_lines becomes a directory of the same name, test_<module>/, holding test_<area>.py files, a conftest.py and a <module>_helpers.py they share. Because the tree mirrors the source, two suites can hold files with the same basename, so run pytest with --import-mode=importlib.

The rules

Area A test file fails when
Discovery fewer than min_test_files were found, or a suite holds none, so the rules would be guarding nothing
Hygiene a test, test method, helper or constant is defined twice; an import sits below the header; it is longer than max_lines; it holds no test
Quality a test checks nothing; every check sits in a branch, loop or try; an assertion is true by construction; it only checks is not None or isinstance; an except swallows silently; a raising callable is built from (_ for _ in ()).throw(); a helper is copied between files
Layout a split package holds no test modules, hides tests in a file pytest won't collect, covers no real module or has a single-file twin; a directory mirrors nothing in the repository
Programs a program is checked for at run time instead of by a marker; a marker names a program by anything but a string literal; a test uses a program fixture without declaring its programs; subprocess runs a program the file never declares; an integration file declares no program at all
Signatures an override in signature_packages renames, drops, reorders or changes the default of a parameter of the method it overrides
Pylint pylint reports anything in the tests, with the shipped rcfile or pylint_rcfile; the library breaks a library_rules rule

A check counts as an assert, a mock's assert_*, pytest.fail, a pytest.raises or pytest.warns block, or a call to a helper that makes one, including helpers imported by name from a *_helpers.py beside the test or above it.

Required programs

pytestmark = pytest.mark.requires_binary("docker")       # whole module

@pytest.mark.requires_binary("gpg", "xorriso|mkisofs")   # "a|b" takes either
def test_sign_image(): ...

A repo with its own kind of requirement, such as tools it installs itself, names the marker in requirement_markers and says how to check it from its conftest.py:

def pytest_testrex_requirement_checkers(config):
    return {"requires_tool": lambda names: [f"{name} (tool)" for name in names if not installed(name)]}

Settings

Every path is relative to the directory holding pyproject.toml. A setting the plugin doesn't know, or one of the wrong type, stops the run.

Setting Default What it does
tests_dir "Tests" Where the suites live
suites ["unit", "integration"] The suites under tests_dir
integration_suites ["integration"] Suites whose files must declare the programs they need
meta_dirs ["unit/meta"] Directories that mirror nothing on purpose
data_dirs [] Directories under tests_dir holding data, never read as tests
source_extensions [".py", ".sh"] What a split test_<name>/ package may cover besides a directory
max_lines 900 The longest a test file may be
min_test_files 1 Fewer test files means discovery is looking in the wrong place
requirement_markers ["requires_binary"] Markers that declare a needed program; any beyond requires_binary need a hook
fixture_programs {} {fixture: [programs]} a test using the fixture must declare, beyond shell_sandbox and shell_traced_env
pylint_rcfile the shipped one Pylint config for the tests
library_targets [] Library paths held to library_rules
library_rules ["unspecified-encoding"] Pylint rules the library's own linter lacks
signature_packages [] Packages whose overrides must keep their base method's signature
sealed_programs [] Programs a test may never start for real
block_network true Refuse in-process outbound connections unless a test is marked allow_network

Developing

python3 -m venv .venv && . .venv/bin/activate
pip install --require-hashes --no-deps -r requirements-dev.txt
pip install --no-deps --no-build-isolation -e .
pytest -q

The library is held to its own rules: Tests/unit/meta/test_testrex.py runs the suite over these tests, and Tests/unit/pytest_testrex/test_suite.py checks that a clean sample repository passes every rule and a broken one fails every rule. CI runs through NearlyTRex/Workflows at 100% line and branch coverage.

Releasing

Run Prepare Release from the Actions tab with patch, minor, major or an exact version. It opens a pull request bumping pyproject.toml; merging it tags the version, publishes a GitHub Release with the sdist and wheel, and publishes them to PyPI through trusted publishing from the pypi environment.

Metadata

Release files for pytest-testrex 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-testrex 0.1.0
File Size Uploaded Provenance
pytest_testrex-0.1.0.tar.gz 26.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-testrex 0.1.0
File Interpreter ABI Platform
pytest_testrex-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 54.5 kB

Release files / pytest_testrex-0.1.0.tar.gz

Download URL pytest_testrex-0.1.0.tar.gz
Size 26.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5698a2968e56e0224f0702b8f2300bc8ca38360c0ef722ea85f0a62c56bf8456
BLAKE2b-256 checksum
How to use checksums
48c490cc7d070570ba0cf86a12bb67ca8a8e8d244e443f72ba59aa3299673e5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / pytest_testrex-0.1.0-py3-none-any.whl

Download URL pytest_testrex-0.1.0-py3-none-any.whl
Size 28.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
03ca4fdd10143343625e4b1c21f9b9b9e23634e68de954e4411284e511dcded3
BLAKE2b-256 checksum
How to use checksums
4f9c02493f2d3b3727e3ef80985c38be25f9390b56c665f5723d3eb9c3801594
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for pytest-testrex 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pytest_testrex-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page