Перейти к содержимому
К программе курса
Pytest с нуля: тесты, которые реально работают
2 / 1118%

📂 Структура проекта и импорты без боли

25 минут

⚠️ Урок устарел. Свежая версия в курсе pytest-basics: Базовая структура: где хранить тесты.

Цель урока

✅ Научиться организовывать код, когда модулей больше одного
✅ Запускать тесты без ModuleNotFoundError одной командой
✅ Настроить IDE один раз и забыть про проблемы с импортами

Шаг 1. Зачем вообще нужна структура?

Пока был один файл — всё работало. Но в реальности появляется второй модуль.

bank-app/
  src/
    bank_account.py      # основной класс
    transaction_log.py   # логирование операций ← новый модуль
    email_sender.py      # уведомления ← ещё один
  tests/
    test_bank_account.py

src/transaction_log.py:

class TransactionLog:
    def __init__(self):
        self.transactions = []
 
    def log(self, message):
        self.transactions.append(message)

src/bank_account.py:

from .transaction_log import TransactionLog
 
class BankAccount:
    def __init__(self):
        self.balance = 0
        self.transaction_log = TransactionLog()
 
    def deposit(self, amount):
        self.balance += amount
        self.transaction_log.log(f"Deposited {amount}")

Без понятной структуры модули не смогли бы импортировать друг друга.

Шаг 2. Минимальная рабочая структура

Создайте базовый скелет:

bank-app/
  src/
    bank_account.py
  tests/
    test_bank_account.py

tests/test_bank_account.py:

from src.bank_account import BankAccount  # импорт через src!
 
def test_deposit():
    account = BankAccount()
    account.deposit(100)
    assert account.balance == 100

Запуск ВСЕГДА из корня проекта:

# PYTHONPATH=. говорит Python: «ищи модули в текущей папке»
# Без этого: Python не видит src/ как пакет
# С этим: работает import from src.bank_account ...
PYTHONPATH=. pytest -v

Шаг 3. Настройка IDE (выберите свою)

VS Code:

# В корне проекта создайте .env
echo "PYTHONPATH=." > .env
# Перезапустите VS Code — готово

PyCharm:

1) Правый клик на корне проекта
2) Mark Directory as → Sources Root
3) Готово — импорты работают

Шаг 4. Алиас для удобства

Добавьте в ~/.zshrc или ~/.bashrc:

alias pt='PYTHONPATH=. pytest -v'

Теперь вместо длинной команды — просто pt.

Проверка себя

❌ Неправильно: запускать pytest из tests/
✅ Правильно: из корня проекта (pwd показывает папку с src/ и tests/)

❌ Неправильно: from bank_account import BankAccount
✅ Правильно: from src.bank_account import BankAccount

Шаг 5. Src layout — профессиональный стандарт

Проблема flat layout: тесты могут случайно импортировать локальные модули вместо установленного пакета. При установке через pip install . пакет может вести себя иначе, чем локально.

Решение: Src layout изолирует исходный код в отдельной папке src/.

Flat layout (простой, для обучения)

my_project/
  my_package/
    __init__.py
    app.py
  tests/
    test_app.py
  pyproject.toml

Плюсы: Просто, быстро начать Минусы: Тесты могут импортировать неустановленный код

Src layout (профессиональный, для production)

my_project/
  src/
    my_package/
      __init__.py
      app.py
  tests/
    test_app.py
  pyproject.toml

Плюсы:

  • Тесты работают с установленным кодом (как это будет в production)
  • Изоляция: невозможно импортировать неустановленный код
  • Стандарт в Python-сообществе (рекомендация PyPA)

Минусы: Требует pip install -e . для разработки

Пример: переход на src layout

До (flat):

bank-app/
  bank_account.py
  tests/
    test_bank_account.py

После (src):

bank-app/
  src/
    bank_app/
      __init__.py
      bank_account.py
  tests/
    test_bank_account.py
  pyproject.toml

Тесты меняются:

# До
from bank_account import BankAccount
 
# После
from bank_app.bank_account import BankAccount

Шаг 6. Editable install — работа как профи

Проблема: При src layout нужно устанавливать пакет после каждого изменения.

Решение: Editable install (pip install -e .) — один раз установили, код обновляется автоматически.

Создайте pyproject.toml

# pyproject.toml в корне проекта
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
 
[project]
name = "bank-app"
version = "0.1.0"
dependencies = []
 
[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-cov>=4.0",
]

Установите в режиме разработки

# Editable install (один раз)
pip install -e .
 
# Установите dev-зависимости
pip install -e ".[dev]"
 
# Теперь pytest работает без PYTHONPATH
pytest -v

Что произошло:

  • Пакет установлен в окружение, но указывает на локальные файлы
  • Изменения в коде сразу видны без переустановки
  • Импорты работают везде (терминал, IDE, pytest)

Сравнение подходов

ПодходКоманда запускаПлюсыМинусы
PYTHONPATHPYTHONPATH=. pytestБыстро начатьНестабильно, не работает везде
Editable installpytest (один раз настроил)Профессионально, стабильноНужен pyproject.toml

Шаг 7. Практика: настройте свой проект

Задача: Создайте проект с src layout и editable install.

# 1. Создайте структуру
mkdir -p my_todo/src/todo my_todo/tests
cd my_todo
 
# 2. Создайте __init__.py
touch src/todo/__init__.py
 
# 3. Создайте код
cat > src/todo/app.py << 'EOF'
class TodoList:
    def __init__(self):
        self.tasks = []
 
    def add(self, task):
        self.tasks.append(task)
        return len(self.tasks)
EOF
 
# 4. Создайте тест
cat > tests/test_app.py << 'EOF'
from todo.app import TodoList
 
def test_add_task():
    todo = TodoList()
    count = todo.add("Learn pytest")
    assert count == 1
    assert todo.tasks == ["Learn pytest"]
EOF
 
# 5. Создайте pyproject.toml
cat > pyproject.toml << 'EOF'
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
 
[project]
name = "todo"
version = "0.1.0"
 
[project.optional-dependencies]
dev = ["pytest>=7.0"]
EOF
 
# 6. Установите в editable режиме
pip install -e ".[dev]"
 
# 7. Запустите тесты (без PYTHONPATH!)
pytest -v

Результат: Тесты проходят, импорты работают, код готов к production.

Проверка себя

✅ Понимаю разницу между flat и src layout
✅ Создал pyproject.toml для своего проекта
✅ Установил пакет через

❌ Неправильно: запускать pytest из tests/ ✅ Правильно: из корня проекта (pwd показывает папку с src/ и tests/)

❌ Неправильно: PYTHONPATH=. pytest для каждого запуска ✅ Правильно: один раз pip install -e ., потом просто pytest

Частые вопросы

Вопрос 1: Обязательно ли использовать src layout для обучения? Ответ: Нет. Flat layout проще для начала. Но для реальных проектов лучше сразу src.

Вопрос 2: Что делать, если pip install -e . не работает? Ответ: Проверьте:

  • pyproject.toml в корне проекта
  • Правильный build-backend (setuptools)
  • Виртуальное окружение активировано

Вопрос 3: Нужно ли переустанавливать после изменения кода? Ответ: Нет! Editable install (-e) автоматически видит изменения.

Что дальше

Импорты настроены профессионально. Следующий урок — тестируем классы и ошибки без try/except в тестах и с первой фикстурой для повторяемого setup.

📂 Структура проекта и импорты без боли — Pytest с нуля: тесты, которые реально работают — Potapov.me