CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/pytest-tests

Configures and runs pytest - the de facto Python test framework with fixture-based dependency injection (`@pytest.fixture` with scopes module/session/function), parametrize for table-driven tests (`@pytest.mark.parametrize`), markers (`@pytest.mark.skip` / `xfail` / `slow`), `conftest.py` for shared fixtures, plugin ecosystem (pytest-cov, pytest-asyncio, pytest-mock, pytest-xdist), `--lf`/`--ff` for fail-loop, coverage gating. Use when working with Python and needing the modern test framework.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files
name:
pytest-tests
description:
Configures and runs pytest - the de facto Python test framework with fixture-based dependency injection (`@pytest.fixture` with scopes module/session/function), parametrize for table-driven tests (`@pytest.mark.parametrize`), markers (`@pytest.mark.skip` / `xfail` / `slow`), `conftest.py` for shared fixtures, plugin ecosystem (pytest-cov, pytest-asyncio, pytest-mock, pytest-xdist), `--lf`/`--ff` for fail-loop, coverage gating. Use when working with Python and needing the modern test framework.

pytest-tests

Overview

Per docs.pytest.org/en/stable:

pytest is the de facto Python test framework. Unlike stdlib unittest, it uses function-style tests (no TestCase), fixture-based dependency injection, parametrize for data-driven tests, and plain-assert rewriting for diff-rich failures.

Lifecycle scope: configure / run / fixtures / mocking / coverage / CI. Test code hygiene (assertions, AAA, mocking anti-patterns) is in test-code-conventions (qa-test-review plugin).

When to use

  • Modern Python project; pytest is the default for new code.
  • Migrating from unittest (mostly mechanical via interop).
  • Need fixture-based dependency injection + parametrize.

Step 1 - Install

pip install pytest
# Common plugins:
pip install pytest-cov pytest-asyncio pytest-mock pytest-xdist

Step 2 - First test

# test_sum.py
def sum(a, b):
    return a + b

def test_adds_1_and_2():
    assert sum(1, 2) == 3
pytest

pytest auto-discovers via test_*.py / *_test.py filenames and test_* / Test* function/class names.

Step 3 - Configuration

pytest.ini (or pyproject.toml [tool.pytest.ini_options] / setup.cfg [tool:pytest]):

# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-ra --strict-markers --strict-config"
markers = [
    "slow: marks tests as slow (deselect with -m 'not slow')",
    "integration: marks tests requiring DB/external resources",
]

--strict-markers rejects undeclared marker names - catches typos like @pytest.mark.skipp (silently skipped before).

Step 4 - Fixtures

import pytest

@pytest.fixture
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()

@pytest.fixture(scope="session")
def app_config():
    return load_config()

@pytest.fixture(autouse=True)
def reset_state():
    yield
    cleanup_after_test()

def test_user_creation(db_connection, app_config):
    user = create_user(db_connection, app_config)
    assert user.id is not None

Fixture scopes: function (default), class, module, package, session. Choose narrowest scope that doesn't waste setup time.

conftest.py shares fixtures across multiple test files in the same directory (and subdirectories).

Step 5 - Parametrize

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_sum_parametrized(a, b, expected):
    assert sum(a, b) == expected

Multi-param multiply (cross-product):

@pytest.mark.parametrize("x", [1, 2, 3])
@pytest.mark.parametrize("y", ['a', 'b'])
def test_combinations(x, y):
    # runs 6 times: (1,'a'), (1,'b'), (2,'a'), ...
    pass

Step 6 - Markers + skip/xfail

@pytest.mark.skip(reason="Requires staging DB")
def test_skip_example():
    pass

@pytest.mark.skipif(sys.version_info < (3, 11), reason="Python 3.11+ syntax")
def test_modern_syntax():
    pass

@pytest.mark.xfail(reason="Known bug; tracked in JIRA-1234")
def test_known_failure():
    assert 1 == 2

@pytest.mark.slow
def test_long_running():
    pass

Filter: pytest -m "not slow" skips slow-marked tests.

Step 7 - Mocking with pytest-mock

def test_with_mock(mocker):
    mock_api = mocker.patch('mymodule.api_client.fetch')
    mock_api.return_value = {'id': 1, 'name': 'Alice'}

    result = my_function()
    mock_api.assert_called_once_with('/users')
    assert result == {'id': 1, 'name': 'Alice'}

mocker fixture from pytest-mock wraps unittest.mock.patch with auto-cleanup at test end.

Step 8 - Async (pytest-asyncio)

import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_function():
    result = await fetch_data()
    assert result == 'expected'

# Or set asyncio_mode = "auto" in pyproject.toml to skip the marker

Step 9 - Coverage with pytest-cov

pytest --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml \
       --cov-fail-under=80

--cov-fail-under=N fails the run if coverage drops below N%.

Full [tool.coverage.*] config, CI wiring, and parallel execution: references/coverage-and-ci.md.

Step 10 - Fast-feedback flags

pytest --lf            # only re-run last-failed tests
pytest --ff            # run last-failed first, then the rest
pytest -x              # stop on first failure
pytest -k "name_pat"   # only tests matching name pattern
pytest -v              # verbose
pytest -s              # don't capture stdout (see print() output)
pytest -p no:cacheprovider   # disable test-cache (CI cache-clean runs)

Anti-patterns

Anti-patternWhy it failsFix
Use setUp / tearDown (TestCase style) instead of fixturesLoses dependency injection benefitsUse fixtures (Step 4)
Skip --strict-markersTypos in markers silently skip testsAlways set in config (Step 3)
Fixture with scope='session' for stateful resourcesState leaks across testsFunction-scope unless setup expensive
pytest -k 'expr' in CI to skip "slow" testsBrittle string matchUse -m markers (Step 6)
Skip --cov-fail-under in CICoverage drops silently over timeAlways gate coverage (Step 9)

Limitations

  • Plugin ecosystem is large; conflicting plugins can cause subtle issues.
  • Fixture-scope reasoning has learning curve.
  • assert rewriting requires pytest's importer; some patterns (running tests as scripts) bypass it.
  • Async support requires pytest-asyncio; built-in support is limited.

References

  • pt-docs - official documentation
  • pytest.org - landing
  • pypi.org/project/pytest-mock - pytest-mock plugin
  • pypi.org/project/pytest-asyncio - async support
  • pypi.org/project/pytest-xdist - parallel execution
  • unittest-tests, doctest-tests, nose2-tests - sister tools
  • test-code-conventions - test code hygiene
Workspace
testland
Visibility
Public
Created
Last updated
Publish Source
GitHub
Badge
testland/pytest-tests badge