Learning Platform
Глоссарий Troubleshooting
Урок 09.06 · 22 мин
Средний
pytestconftest.pyAuto-discoverysrc-layoutflat-layoutpyproject.tomlpytest.initool.pytest.ini_optionsProject layouts

conftest.py, project layouts, pyproject.toml — discovery configuration

В этом уроке — infrastructure layer pytest: где хранить shared fixtures, как организовать project structure, и какие настройки vital для production-grade test suite.

В этом уроке:

  1. conftest.py — special file pytest auto-discovers БЕЗ import.
  2. Fixture sharingconftest.py propagates fixtures recursively во все test files под directory.
  3. Project layoutssrc-layout vs flat-layout (pros/cons).
  4. pyproject.toml [tool.pytest.ini_options] — modern config (vs legacy pytest.ini).
  5. testpaths, python_files, markers, addopts — main config keys.
  6. Browser challenge framing — Pattern 4 string-analysis (triple-quote conftest content).

conftest.py — special file pytest auto-discovers

conftest.py — magic file name pytest автоматически загружает при collection. No explicit import needed:

project/
├── pyproject.toml
├── tests/
│   ├── conftest.py             ← shared fixtures для tests/
│   ├── test_users.py           ← uses fixtures from tests/conftest.py automatically
│   ├── test_orders.py          ← uses fixtures from tests/conftest.py automatically
│   └── integration/
│       ├── conftest.py         ← additional fixtures для tests/integration/
│       └── test_api.py         ← inherits fixtures from BOTH conftest.py files
└── src/mypkg/...

tests/conftest.py:

# tests/conftest.py
import pytest

@pytest.fixture(scope='session')
def database():
    """Shared session-scoped DB fixture."""
    db = Database(':memory:')
    db.run_migrations()
    yield db
    db.drop_all()


@pytest.fixture
def alice(database):
    """Per-test Alice user fixture."""
    user = database.insert_user('alice')
    yield user
    database.delete_user(user.id)

tests/test_users.py:

# tests/test_users.py
# NO import from conftest needed — pytest auto-injects

def test_alice_exists(alice):
    assert alice.name == 'alice'

tests/test_users.py использует fixture alice без import — pytest автоматически discovers и injects.

Recursive propagation: tests/integration/test_api.py имеет access к обеим:

  • Fixtures из tests/conftest.py (parent directory).
  • Fixtures из tests/integration/conftest.py (own directory).

Override — если в tests/integration/conftest.py есть fixture с тем же именем, она shadows parent’s fixture для тестов в этой directory.

Cite: pytest 8 docs — conftest.py; _pytest/config/__init__.py — discovery machinery.


Project layouts — src-layout vs flat-layout

Two main project structures для Python projects:

project/
├── pyproject.toml
├── src/
│   └── mypkg/
│       ├── __init__.py
│       ├── auth.py
│       └── models.py
└── tests/
    ├── conftest.py
    ├── test_auth.py
    └── test_models.py

Pros:

  • Forced installation — нельзя случайно import mypkg без pip install -e .. Tests always run против installed package, не source tree. Это catches missing manifest entries (e.g., forgot to include resource file in package data).
  • Clean separation — production code в src/, tests в tests/. Build tools (poetry, hatch) предполагают src/ layout.

Cons:

  • Editable install requiredpip install -e . before first test run. Adds setup step.
  • Slight cognitive overhead — import mypkg looks for src/mypkg, not project root.

flat-layout (older, simpler)

project/
├── pyproject.toml
├── mypkg/
│   ├── __init__.py
│   ├── auth.py
│   └── models.py
└── tests/
    ├── conftest.py
    └── test_auth.py

Pros:

  • No install needed — pytest finds mypkg automatically (Python adds CWD to sys.path).
  • Simpler для small projects / scripts / prototypes.

Cons:

  • Hidden bugs — local development imports differ from installed package imports.
  • Tools like tox / hatch may не работают cleanly без extra config.

Pragmatic rule: src-layout для production projects (libraries, services). flat-layout ok для scripts, notebooks, learning projects. Modern toolchain (poetry, hatch, uv) all default to src-layout.

Cite: pytest 8 docs — good integration practices; PyPA src-layout vs flat-layout.


pyproject.toml [tool.pytest.ini_options] — modern config

Modern config goes в pyproject.toml (PEP 518 standard для Python project metadata):

# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "mypkg"
version = "0.1.0"
dependencies = [
    "requests>=2.31",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "integration: marks integration tests requiring external services",
]
addopts = [
    "-ra",                          # show all test outcomes (failed, errors, skipped)
    "--strict-markers",             # fail if test uses unregistered marker
    "--strict-config",              # fail if pytest.ini has unknown options
    "-W error",                     # warnings as errors
]
filterwarnings = [
    "error",
    "ignore::DeprecationWarning:third_party_lib.*",
]

Main config keys:

KeyEffect
testpathsWhere pytest searches for tests (limits discovery scope)
python_filesTest file patterns (default test_*.py)
python_classesTest class patterns (default Test*)
python_functionsTest function patterns (default test_*)
markersRegistered marker names (avoid typos)
addoptsDefault CLI args appended to every pytest invocation
filterwarningsWarning treatment rules

--strict-markers — production gate. Без него @pytest.mark.slowy (typo) silently не applies. С --strict-markers pytest fails: 'slowy' not found in markers configuration option.

-ra — show extra test summary. Default — pytest hides skipped/xfailed details. -ra includes them — useful для CI logs.

Legacy pytest.ini

Pre-PEP 518 config — отдельный INI file:

# pytest.ini (legacy)
[pytest]
testpaths = tests
python_files = test_*.py
markers =
    slow: marks tests as slow
addopts = -ra --strict-markers

Same options, separate file. Pragmatic rule: новые projects — pyproject.toml. Existing — keep pytest.ini если работает.

Cite: pytest 8 docs — config files; PEP 518 — pyproject.toml standard.


Browser challenge framing — Pattern 4 string-analysis

В browser challenges Module 08 для уроков demonstrating pytest syntax (без runtime execution) мы используем Pattern 4 — учащийся пишет conftest.py content в triple-quote string, и solve() верифицирует через substring checks:

# Browser challenge skeleton (py-m08-06-code-1):
conftest_content = '''
# conftest.py — shared fixtures
@pytest.fixture(scope='session')
def db_url():
    return 'sqlite:///:memory:'
'''

def solve(_unused):
    """Verify conftest_content имеет required pieces via substring checks."""
    checks = [
        '@pytest.fixture' in conftest_content,
        "scope='session'" in conftest_content,
        'def db_url' in conftest_content,
        'sqlite:///:memory:' in conftest_content,
    ]
    return f'fixture_decorator={checks[0]}, scope_session={checks[1]}, fn_name={checks[2]}, db_url_value={checks[3]}'

Critical designconftest_content — это string, не actual Python code. Pyodide не executes import pytestimport pytest lives только в triple-quote text (если бы было). Validator (validate-allowed-imports.cjs) checks for line-anchored import pytest patterns ((?:^|\\n)\\s*import\\s+pytest) — но мы скорее избегаем literal import pytest в triple-quote, проверяя '@pytest.fixture' substring instead. Это sidesteps potential validator collision.

Параллель с pytest:

Browser challenge (Pattern 4)Локальный pytest equivalent
conftest_content = '''...''' (string)Файл conftest.py (executable)
'@pytest.fixture' in conftest_content (substring check)Pytest discovery loads conftest.py automatically
solve() returns booleanspytest -v собирает fixtures для tests

Mental model: browser challenge педагогически тренирует syntax recognition — учащийся пишет canonical conftest.py code, even если runtime — string-analysis. Local pytest — drop-in replacement (drop string quotes → save as conftest.py → done).

Cite: 67-RESEARCH.md Pattern 4 lines 707-724.


Recipe — production conftest.py + pyproject.toml

End-to-end production setup:

pyproject.toml — config:

[tool.pytest.ini_options]
testpaths = ["tests"]
markers = [
    "integration: integration tests requiring external services",
    "slow: tests that take >5s",
]
addopts = [
    "-ra",
    "--strict-markers",
    "--strict-config",
]

tests/conftest.py — shared fixtures:

# tests/conftest.py
import pytest
from mypkg.database import Database
from mypkg.user_service import UserService


@pytest.fixture(scope='session')
def app_config():
    """Read-only test config."""
    return {
        'db_url': 'sqlite:///:memory:',
        'log_level': 'DEBUG',
    }


@pytest.fixture(scope='session')
def db_schema(app_config):
    """One DB schema per session — expensive setup."""
    db = Database(app_config['db_url'])
    db.run_migrations()
    yield db
    db.drop_all()


@pytest.fixture
def user_service(db_schema):
    """Per-test UserService — fresh state."""
    return UserService(db_schema)


@pytest.fixture
def alice(user_service):
    """Per-test Alice fixture."""
    user = user_service.register('[email protected]', 'password')
    yield user
    user_service.delete(user.id)

tests/test_user_service.py — uses fixtures без import:

# tests/test_user_service.py
def test_alice_can_login(alice, user_service):
    """Alice (created via fixture) can authenticate."""
    auth = user_service.authenticate(alice.email, 'password')
    assert auth.is_valid is True


@pytest.mark.integration
def test_alice_via_real_smtp(alice):
    """Tagged как integration — `pytest -m integration` runs only these."""
    ...

tests/integration/conftest.pyadditional fixtures для integration tests:

# tests/integration/conftest.py
import pytest

@pytest.fixture(scope='session')
def real_smtp_server():
    """Real SMTP for integration — additional setup."""
    server = MockSMTPServer.start_subprocess()
    yield server
    server.stop()

Что мы получили:

  1. Layered fixtures — session-scoped (config, schema) + function-scoped (services, users). Recall lesson 02 scope hierarchy.
  2. Recursive auto-discoverytests/integration/test_X.py имеет access к обеим conftest files.
  3. Markers + --strict-markers — typo-safe categorization.
  4. CLI ergonomicspytest -m integration runs только integration tests; pytest -m "not slow" skips slow.

Это — production-grade test infrastructure.


Brief — pytest_plugins для plugin loading

В conftest.py или test files можно declare pytest_plugins list — pytest loads them automatically:

# tests/conftest.py
pytest_plugins = [
    'pytest_asyncio',         # для async tests
    'tests.fixtures.db',      # custom fixtures module
]

Это alternative — pip install pytest-asyncio + auto-discovery. Use case: organize fixtures в submodules, plugin loading via config.

Cite: pytest 8 docs — pytest_plugins.


Ключевые выводы

  1. conftest.py — special pytest file, auto-discovered БЕЗ import. Fixtures defined here propagate recursively ко всем test files под directory. Multiple conftest.py — child shadows parent fixture при name collision.
  2. Project layouts: src-layout (recommended) — src/mypkg/ + tests/, forced editable install caught manifest issues. flat-layout — simpler, ok для scripts/prototypes.
  3. pyproject.toml [tool.pytest.ini_options] — modern config (PEP 518 standard). Replaces legacy pytest.ini. Main keys: testpaths, python_files, markers, addopts.
  4. --strict-markers — production gate (catches typos в @pytest.mark.X); -ra — show extra test summary в CI logs.
  5. Browser challenge Pattern 4 (string-analysis triple-quote) — pedagogical technique для demonstrating pytest syntax без runtime execution. conftest_content = '''...''' + substring checks. Same mental model — drop quotes для local file.
  6. Cite: pytest 8 docs — conftest.py / good integration practices; _pytest/config/__init__.py discovery machinery.

Дальше — summary + CI bridge (урок 07): module recap + Run-on-Your-Machine для coverage.py (pip install coverage + coverage run -m pytest + coverage report) + forward-link Phase 69 (Production Skills CI integration).

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. **Apply scenario:** В проекте структура: `tests/conftest.py` (fixture `database`), `tests/integration/conftest.py` (fixture `smtp`), `tests/integration/test_api.py`. К каким fixtures имеет access `test_api.py`?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7