📂 Структура проекта и импорты без боли
⚠️ Урок устарел. Свежая версия в курсе pytest-basics: Базовая структура: где хранить тесты.
Цель урока
✅ Научиться организовывать код, когда модулей больше одного
✅ Запускать тесты без ModuleNotFoundError одной командой
✅ Настроить IDE один раз и забыть про проблемы с импортами
Шаг 1. Зачем вообще нужна структура?
Пока был один файл — всё работало. Но в реальности появляется второй модуль.
bank-app/
src/
bank_account.py # основной класс
transaction_log.py # логирование операций ← новый модуль
email_sender.py # уведомления ← ещё один
tests/
test_bank_account.pysrc/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.pytests/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)
Сравнение подходов
| Подход | Команда запуска | Плюсы | Минусы |
|---|---|---|---|
| PYTHONPATH | PYTHONPATH=. pytest | Быстро начать | Нестабильно, не работает везде |
| Editable install | pytest (один раз настроил) | Профессионально, стабильно | Нужен 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.
Проверка себя
❌ Неправильно: запускать 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.