Skip to main content

pytest-inline-tdd

A pytest plugin for test-driven inline testing — write tests before the code they verify, right inside your production source files.

Motivation

The Problem with Traditional Unit Tests

Traditional unit tests live in separate test_*.py files, isolated from production code. This creates several issues:

  1. Distance breeds neglect — Tests and source code reside in different files (or directories), making it easy to forget updating tests after code changes.
  2. Coarse granularity — Unit tests typically target entire functions or methods, lacking focused verification of individual statements within a function.
  3. Lost context — Reading test code requires constant switching between test files and source files, making intent harder to follow.

Original pytest-inline (Inline Testing)

pytest-inline introduced the concept of inline testing: placing tests right next to the source code to directly verify a statement's output.

# Original inline testing: test comes AFTER the statement under test
def example(a):
    b = a + 1
    itest().given(a, 1).check_eq(b, 2)  # Code first, test second

This solves the separation problem, but it is still a code-first, test-later workflow.

pytest-inline-tdd: TDD Mode for Inline Testing

pytest-inline-tdd brings Test-Driven Development (TDD) to inline testing. The core idea:

Write the test (expectation) first, then write the implementation. Tests and code are tightly coupled in the same file, the same function.

# TDD inline testing: test comes BEFORE the statement under test
from inline_tdd import itestdd

def example(a):
    itestdd().given(a, 1).check_eq(b, 2)  # Test first: expect b=2 when a=1
    b = a + 1                              # Then implement

Key Differences from Original pytest-inline

Feature pytest-inline pytest-inline-tdd
Test position After the statement under test Before the statement under test
Workflow Code first, test later Test first, code later (TDD)
Package name inline inline_tdd
API name itest() itestdd()
Mindset Verify already-written code Declare expected behavior, then implement
Compatibility Supports both pre/post modes Supports both pre (TDD) and post modes

Use Cases

1. Statement-Level TDD

Write your expectation for a statement first, then implement it — testing and coding happen together:

from inline_tdd import itestdd

def calculate_discount(price, rate):
    itestdd().given(price, 100).given(rate, 0.2).check_eq(discount, 20.0)
    discount = price * rate
    
    itestdd().given(price, 100).given(discount, 20.0).check_eq(final, 80.0)
    final = price - discount
    
    return final

2. Branch Verification for Complex Control Flow

Independently verify each branch of if/elif/else, for, and while statements:

def classify(a):
    itestdd().given(a, 15).check_eq(b, "large")
    itestdd().given(a, 5).check_eq(b, "medium")
    itestdd().given(a, -1).check_eq(b, "small")
    if a > 10:
        b = "large"
    elif a > 0:
        b = "medium"
    else:
        b = "small"

3. Step-by-Step Data Pipeline Verification

Test each transformation step in place, ensuring every stage of a pipeline behaves as expected:

import numpy as np
from inline_tdd import itestdd

def normalize(data):
    itestdd().given(data, np.array([2.0, 4.0, 6.0])).check_eq(m, 4.0)
    m = np.mean(data)
    
    itestdd().given(data, np.array([2.0, 4.0, 6.0])).given(m, 4.0).check_eq(
        centered.tolist(), [-2.0, 0.0, 2.0])
    centered = data - m
    
    return centered

4. Database Operation Verification

Inline tests work well for verifying the results of SQL queries and operations:

import sqlite3
from inline_tdd import itestdd

def count_users(conn):
    itestdd().check_eq(n, 3)
    n = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
    return n

5. Parameterized Tests

Cover multiple input-output pairs in a single statement:

def double(a):
    itestdd(parameterized=True).given(a, [1, 2, 3]).check_eq(b, [2, 4, 6])
    b = a * 2

Install

pip install pytest-inline-tdd

Use

# Run all inline tests in the current directory
pytest .

# Run inline tests in a specific file
pytest path/to/file.py

# Run tests with a specific tag
pytest . --inline-group tag_name

API

Declaring an Inline Test

itestdd(test_name, parameterized, repeated, tag, disabled, timeout)
Parameter Type Default Description
test_name str filename + line number Name of the test
parameterized bool False Whether the test is parameterized
repeated int 1 Number of times to repeat the test
tag list [] Tags for grouping and filtering
disabled bool False Whether the test is disabled
timeout float -1.0 Timeout in seconds (-1 for no limit)

Preconditions: assume

itestdd().assume(condition).given(...).check_eq(...)

If condition is False, the test is skipped. Must appear before any given() calls, and only one assume() is allowed per test.

Test Inputs: given

itestdd().given(variable, value)

Multiple given() calls can be chained. Assigns test input values to variables used in the statement under test.

Test Assertions: check_*

Method Description
check_eq(actual, expected) Equal
check_neq(actual, expected) Not equal
check_true(expr) Expression is true
check_false(expr) Expression is false
check_none(var) Value is None
check_not_none(var) Value is not None
check_same(a, b) Same object (is)
check_not_same(a, b) Different objects
fail() Force failure

Only one check assertion is allowed per inline test.

Stripping Inline Tests

When deploying to production, you can remove (comment) all inline_tdd related code (imports and itestdd() calls) from your source files using the built-in strip-itestdd command:

# Print cleaned code to stdout
strip-itestdd path/to/file.py

# Edit the file in-place
strip-itestdd path/to/file.py -i

# Write cleaned output to a new file
strip-itestdd path/to/file.py -o clean_file.py

You can also run it as a module:

python -m inline_tdd.strip path/to/file.py -i

Before:

from inline_tdd import itestdd

def example(a):
    itestdd().given(a, 1).check_eq(b, 2)
    b = a + 1
    return b

After:

# from inline_tdd import itestdd

def example(a):
    # itestdd().given(a, 1).check_eq(b, 2)
    b = a + 1
    return b

Performance

Inline tests are fast — each test verifies only a single statement. In non-testing mode (i.e., normal production execution), all itestdd() calls behave as no-op function calls with negligible overhead.

Notice

Do not name functions containing inline tests with a test_ prefix. Pytest automatically collects functions whose names start with test_ as regular test cases and tries to resolve their parameters as fixtures. If the function has parameters that are only used with itestdd().given(...), pytest will fail with a fixture 'xxx' not found error. Use a non-test_ name (e.g., compute, my_func) to avoid this conflict.

Citation

This project builds on the following research:

Title: Inline Tests

Authors: Yu Liu, Pengyu Nie, Owolabi Legunsen, Milos Gligoric

@inproceedings{LiuASE22InlineTests,
  title =        {Inline Tests},
  author =       {Yu Liu and Pengyu Nie and Owolabi Legunsen and Milos Gligoric},
  pages =        {1--13},
  booktitle =    {International Conference on Automated Software Engineering},
  year =         {2022},
}

Title: pytest-inline

Authors: Yu Liu, Zachary Thurston, Alan Han, Pengyu Nie, Milos Gligoric, Owolabi Legunsen

@inproceedings{LiuICSE23PytestInline,
  title =        {pytest-inline: An Inline Testing Tool for Python},
  author =       {Yu Liu and Zachary Thurston and Alan Han and Pengyu Nie and Milos Gligoric and Owolabi Legunsen},
  pages =        {1--4},
  booktitle =    {International Conference on Software Engineering, DEMO},
  year =         {2023},
}

Release files for pytest-inline-tdd 1.2.10

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-inline-tdd 1.2.10
File Size Uploaded
pytest_inline_tdd-1.2.10.tar.gz 52.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-inline-tdd 1.2.10
File Interpreter ABI Platform
pytest_inline_tdd-1.2.10-py3-none-any.whl Python 3 none any Details

Total release size: 85.8 kB

Release files / pytest_inline_tdd-1.2.10.tar.gz

Download URL pytest_inline_tdd-1.2.10.tar.gz
Size 52.7 kB
Tags Source
SHA-256 checksum
How to use checksums
911a19b9ecac232bdeaf8c8ba712e608949e875b3cb728c0cd16faa6e0d702b8
BLAKE2b-256 checksum
How to use checksums
bea2d683353d2ed3f97a2c00881bec245bcc78c3e09235ec47883d9bad732f47
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.25

Release files / pytest_inline_tdd-1.2.10-py3-none-any.whl

Download URL pytest_inline_tdd-1.2.10-py3-none-any.whl
Size 33.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6afa0a164b3136acd4c6ecb2778afb6b36b8b5dc7b45d45f864b6a885cff8eac
BLAKE2b-256 checksum
How to use checksums
d05a4121dee82c6711625d78fbd5d8bc70b78adfab523da40f00b7d7ed76afb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.25

Release history Release notifications | RSS feed

This release

1.2.10 This release

2 release files

1.2.9

2 release files

1.2.8

2 release files

1.2.7

2 release files

1.2.6

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

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