Skip to main content

inline-tests

"Just itest it."
"Does it pass the (eye) itest?" 👁️

inline-tests

PyPI Python License CI


The Problem

Tests live in one place. Code lives in another. You change a function and forget to update its test. Or you write the test later. Or never.

The test file mirrors the source file. src/auth/login.py becomes tests/auth/test_login.py. Two parallel hierarchies drifting apart.

Rust solved this years ago. Tests live next to the code they test. You see them together. You change them together. They can't drift because they're in the same file.

Python never had this. Until now.

The Solution

# auth/login.py
from inline_tests import test

def authenticate(user, password):
    if not user or not password:
        return None
    return verify_credentials(user, password)

@test
def rejects_empty_credentials():
    assert authenticate("", "pass") is None
    assert authenticate("user", "") is None

@test
def accepts_valid_credentials():
    result = authenticate("admin", "secret")
    assert result is not None
itest

Install

uv tool install inline-tests

This gives you itest everywhere. For one-off use: uvx inline-tests.

Other install methods
# As a project dependency
uv add inline-tests --group dev
pip install inline-tests

# With extras
uv tool install inline-tests[full]        # everything
uv tool install inline-tests[essentials]  # async, mock, coverage

Why This Works

Tests can't go stale. When you change the function, the test is right there. You can't miss it.

Testing becomes part of writing code. Not a separate phase. Not something you do after. You think about behavior while you're defining it.

You see the relationship. Test and implementation side by side. You notice when tests check implementation details instead of behavior. You notice missing edge cases.

No ceremony. No mirroring directory structures. No hunting for the right test file. No context switching. Just @test and you're done.

Features

Everything pytest offers, because this is pytest.

from inline_tests import test, it

# BDD style
@it
def should_handle_empty_input():
    assert process("") == []

# Async
@test
async def fetches_data():
    result = await fetch("https://api.example.com")
    assert result.status == 200

# Fixtures
@test
def writes_to_disk(tmp_path):
    f = tmp_path / "test.txt"
    f.write_text("hello")
    assert f.read_text() == "hello"

# Parametrize
@test
@pytest.mark.parametrize("x,expected", [(1, 1), (2, 4), (3, 9)])
def squares_correctly(x, expected):
    assert x * x == expected

# Test classes (no decorator needed on class)
class ValidationSuite:
    @test
    def rejects_negative(self):
        assert validate(-1) is False

Extras

Extra What you get
async pytest-asyncio, anyio, pyleak
mock pytest-mock
cov pytest-cov
parallel pytest-xdist
bench pytest-benchmark
property hypothesis
http pytest-httpx
data faker
essentials async + mock + cov
full all of the above

How It Works

The @test decorator marks functions with a hidden attribute. When you run itest, the plugin scans Python files for this marker using AST parsing. No imports happen until a file actually contains tests. Then pytest collects and runs them normally.

Standard test_*.py files work as usual. This is additive.

Production

Just ship it. The @test decorator is a no-op at runtime. Tests never execute unless you run itest.

Or strip tests for minimal deployments:

itest strip src/ -o dist/
uv build dist/

Removes @test functions via AST. Original files untouched.

Development

git clone https://github.com/dedalus-labs/inline-tests-python
cd inline-tests
uv sync
uv run pytest

Built on a modern Python stack. See CONTRIBUTING.md.

License

MIT

Links


Dedalus Labs © 2026.

Release files for inline-tests 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 inline-tests 0.1.0
File Size Uploaded
inline_tests-0.1.0.tar.gz 11.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for inline-tests 0.1.0
File Interpreter ABI Platform
inline_tests-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 23.8 kB

Release files / inline_tests-0.1.0.tar.gz

Download URL inline_tests-0.1.0.tar.gz
Size 11.1 kB
Tags Source
SHA-256 checksum
How to use checksums
77b66c7073a7e554a350dac7dce3138e992d957c3dd86f4c7c4c02ac5baa8d46
BLAKE2b-256 checksum
How to use checksums
400e2c734150511c8df54488dc2d7c221f9aef1cfa91c48babf2d98ee257c2a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.8

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

Download URL inline_tests-0.1.0-py3-none-any.whl
Size 12.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1347cc68ee51e0817bc26757344a9fdc90ef78d153d578d49883681e42713387
BLAKE2b-256 checksum
How to use checksums
e3cf622eabd55905f3f55feecf80727a1bb245a764b1df49f2399ba6147fc7bc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.8

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

0.0.1

2 release files

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