Skip to main content

assert-one-assert-per-pytest

Assert that each pytest test function contains exactly one assert.

Why One Assert Per Test?

The "one assert per test" pattern encourages writing focused, atomic tests that verify a single behavior. Benefits include:

  • Clear failure messages: When a test fails, you know exactly what behavior broke
  • Better test names: Tests naturally describe specific behaviors
  • Easier maintenance: Changes to one behavior don't affect unrelated assertions
  • Faster debugging: No need to hunt through multiple assertions

Installation

pip install assert-one-assert-per-pytest

Usage

Command Line

# Scan specific files
assert-one-assert-per-pytest test_example.py

# Scan directories recursively
assert-one-assert-per-pytest tests/

# Use glob patterns
assert-one-assert-per-pytest "tests/**/test_*.py"

# Exclude patterns
assert-one-assert-per-pytest tests/ --exclude "**/conftest.py"

# Verbose output
assert-one-assert-per-pytest tests/ --verbose

# Fail fast (exit on first finding)
assert-one-assert-per-pytest tests/ --fail-fast

# Warn only (always exit 0)
assert-one-assert-per-pytest tests/ --warn-only

GitHub Actions

- uses: 10U-Labs-LLC/assert-one-assert-per-pytest@v1
  with:
    files: "tests/"
    exclude: "**/conftest.py"
    verbose: "true"

As a Python Module

python -m assert_one_assert_per_pytest tests/

Output Format

Default output shows one finding per line:

path/to/test_file.py:10:test_example:0
path/to/test_file.py:25:test_another:3
path/to/test_file.py:31:test_third:2:conjunction

Format: file_path:line_number:function_name:assert_count

A conjunction finding adds a fifth field, conjunction. Its line number is the assert statement rather than the enclosing function, and its count is the number of conjuncts the expression joins.

Exit Codes

  • 0: No findings (or --warn-only specified)
  • 1: Findings detected
  • 2: Error (missing files, syntax errors, etc.)

What Counts as an Assert?

This tool counts Python assert statements at the immediate level of test functions. It also counts:

  • pytest.raises() context managers
  • pytest.warns() context managers

It does not count:

  • Assertions in nested functions or classes
  • Helper assertions in fixtures or utility functions

Conjunctions

An assert whose test expression is a top-level and carries one claim per conjunct, so a red run names the whole expression rather than the claim that broke. The tool reports it the way it reports a second assert.

def test_bucket():
    assert "bucket_name" in result and "bucket_arn" in result

Only a top-level and is refused. These are all allowed:

  • assert a or b, one claim about two alternatives. Splitting it into two tests would assert something stronger than the author meant.
  • assert 0 < x < 10, a comparison chain, which is one claim about one value whatever the length of the chain.
  • assert not (a and b), which is not a or not b, and so a disjunction.
  • assert (a and b) or c, where the claim being made is the disjunction.
  • assert all(i.a and i.b for i in items), where the and is evaluated per item inside a comprehension rather than joining two claims about the test.
  • assert a, "b and c", where the and is inside a string.

A short-circuit guard is refused along with the rest, because nothing in the expression distinguishes a guard from a claim. Drop the guard and let the failure speak for itself: assert len(blocks) == 1 raises on None and names the line, where assert blocks is not None and len(blocks) == 1 does not.

Options

Option Description
--exclude PATTERNS Glob patterns to exclude (comma-separated)
--quiet Suppress all output (exit code only)
--verbose Show detailed processing information
--fail-fast Exit after first finding
--warn-only Always exit 0, even with findings

License

Apache 2.0 - See LICENSE.txt

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

assert_one_assert_per_pytest-20260830063341.tar.gz (27.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file assert_one_assert_per_pytest-20260830063341.tar.gz.

File metadata

File hashes

Hashes for assert_one_assert_per_pytest-20260830063341.tar.gz
Algorithm Hash digest
SHA256 120380b382dd9d430d31f910f2e141555200e223bec31f83798be15e8b631350
MD5 04d02a9b3db6c74929d95e8e36030c5b
BLAKE2b-256 96372e68819f4de617915f06a453713ff3d294bd0a2d1952f1074e7bcb4f7313

See more details on using hashes here.

Provenance

The following attestation bundles were made for assert_one_assert_per_pytest-20260830063341.tar.gz:

Publisher: release.yml on 10U-Labs/assert-one-assert-per-pytest

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file assert_one_assert_per_pytest-20260830063341-py3-none-any.whl.

File metadata

File hashes

Hashes for assert_one_assert_per_pytest-20260830063341-py3-none-any.whl
Algorithm Hash digest
SHA256 413375767ed52ca2c8a07f38489ce641f1e1790d9d5892eb98479e988c49b03a
MD5 74461ee69814dc05b251fabda688f89b
BLAKE2b-256 ca563a82ef97535c6131f2250b0cadcf4142c9bf270304d4afcbdc5e2dea264f

See more details on using hashes here.

Provenance

The following attestation bundles were made for assert_one_assert_per_pytest-20260830063341-py3-none-any.whl:

Publisher: release.yml on 10U-Labs/assert-one-assert-per-pytest

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

20260830063341 This release

2 files

20260830060051

2 files

20260830055519

2 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