Skip to content

The Decorator

Why a decorator?

You might wonder: why not just name functions test_* and let pytest collect them?

The decorator makes inline tests opt-in. Without --inline-tests, the plugin doesn't import your source files at all. It scans for the @test decorator via AST first. No decorator? The file is skipped. No import, no overhead, no accidental test runs.

This matters:

  • You control when inline tests run. pytest runs your regular test suite. itest adds inline tests. Two modes, explicit choice.
  • No pollution. Your CI can run pytest tests/ without touching source files.
  • Naming freedom. Write rejects_empty_input() instead of test_rejects_empty_input().
  • Method-level granularity. Put @test on methods in regular classes without needing a TestFoo class.

Basic usage

from inline_tests import test

@test
def my_test():
    assert True

The decorator marks the function. Nothing else changes. The function still exists at runtime and can be called normally.

@it alias

For BDD-style naming:

from inline_tests import it

@it
def should_return_empty_list_for_no_input():
    assert process([]) == []

it is literally test. Same function, different name.

With reason

Document why a test exists:

@test(reason="regression: issue #42")
def handles_unicode_input():
    assert parse("é") == "é"

The reason is stored but not currently displayed. Future versions may surface it in reports.

Test classes

Group related tests:

class ValidationTests:
    @test
    def rejects_empty(self):
        assert validate("") is False

    @test
    def accepts_valid(self):
        assert validate("hello") is True

Only the methods need @test. The class is just for organization.

Async tests

Works with pytest-asyncio:

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

Requires inline-tests[async] or pytest-asyncio installed separately.

Production considerations

Unlike Rust's #[cfg(test)], Python can't compile out decorated functions. They exist at runtime.

For zero overhead in production:

try:
    from inline_tests import test
except ImportError:
    test = lambda f=None, **_: f if f else (lambda x: x)

Or simply keep inline-tests as a dev dependency. The decorator itself costs nothing if you don't run itest.