Базовая структура: где хранить тесты
В первом уроке вы запустили тест прямо в корне проекта. Но в реальных проектах тесты хранятся отдельно от кода.
Цель: Научиться правильно организовывать тесты, чтобы проект был понятен вам и вашей команде.
Вы точно готовы?
Убедитесь, что прошли урок 0:
# Вы должны уметь запустить этот тест
def test_simple():
assert 2 + 2 == 4Если не проходили — вернитесь к Первому тесту за 10 минут.
Проблема: тесты в корне проекта
Плохая структура:
my-project/
├── test_user.py
├── test_auth.py
├── test_api.py
├── user.py
├── auth.py
└── api.pyПроблемы:
- ❌ Тесты смешаны с кодом
- ❌ Сложно найти нужный тест
- ❌ При деплое тесты попадут в production
Правильная структура: tests/ директория
Создайте структуру
cd pytest-tutorial
# Создайте директории
mkdir src
mkdir tests
# Структура должна быть такой:
# pytest-tutorial/
# ├── src/ # Код приложения
# ├── tests/ # Тесты
# └── .venv/ # Виртуальное окружениеСоздайте код приложения
# Создайте файл src/calculator.py
touch src/calculator.pyНапишите простой код:
# src/calculator.py
def add(a, b):
"""Складывает два числа"""
return a + b
def subtract(a, b):
"""Вычитает второе число из первого"""
return a - b
def multiply(a, b):
"""Умножает два числа"""
return a * bСоздайте тест
# Создайте файл tests/test_calculator.py
touch tests/test_calculator.pyНапишите тест:
# tests/test_calculator.py
from src.calculator import add, subtract, multiply
def test_add():
"""Тест сложения"""
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_subtract():
"""Тест вычитания"""
assert subtract(5, 3) == 2
assert subtract(0, 5) == -5
def test_multiply():
"""Тест умножения"""
assert multiply(3, 4) == 12
assert multiply(-2, 3) == -6Запуск тестов
Из корня проекта
# Убедитесь что вы в pytest-tutorial/
pwd
# Запустите все тесты
pytest tests/ -vОжидаемый результат:
============================ test session starts ============================
collected 3 items
tests/test_calculator.py::test_add PASSED [ 33%]
tests/test_calculator.py::test_subtract PASSED [ 66%]
tests/test_calculator.py::test_multiply PASSED [100%]
============================= 3 passed in 0.02s =============================✅ Pytest автоматически нашёл все тесты в tests/ директории!
Запуск одного файла
# Запустить только test_calculator.py
pytest tests/test_calculator.py -vЗапуск одного теста
# Запустить только test_add
pytest tests/test_calculator.py::test_add -vПравила именования pytest
Pytest автоматически находит тесты по правилам именования:
Правило #1: Файлы
# ✅ ХОРОШО — pytest найдёт
test_*.py # test_calculator.py, test_user.py
*_test.py # calculator_test.py, user_test.py
# ❌ ПЛОХО — pytest НЕ найдёт
calculator.py # Нет префикса test_
my_tests.py # Нет префикса test_Рекомендация: Используйте test_*.py (стандарт в Python сообществе).
Правило #2: Функции
# ✅ ХОРОШО — pytest найдёт
def test_addition():
pass
def test_user_creation():
pass
# ❌ ПЛОХО — pytest НЕ найдёт
def addition_test(): # Нет префикса test_
pass
def validate_user(): # Нет префикса test_
passПравило #3: Классы (опционально)
# ✅ ХОРОШО — pytest найдёт
class TestCalculator:
def test_add(self):
pass
# ❌ ПЛОХО — pytest НЕ найдёт
class Calculator: # Нет префикса Test
def test_add(self):
passПримечание: Классы для тестов мы разберём в уроке 2.
Pytest Discovery: как pytest находит тесты
Когда вы запускаете pytest, он:
Шаг 1: Ищет директории
pytest-tutorial/
├── tests/ ← Проверяет эту директорию
├── src/ ← Игнорирует (нет тестов)
└── .venv/ ← ИгнорируетШаг 2: Ищет файлы
tests/
├── test_calculator.py ← Находит (начинается с test_)
├── test_user.py ← Находит
└── helpers.py ← Игнорирует (нет префикса test_)Шаг 3: Ищет функции
# tests/test_calculator.py
def test_add(): ← Находит
pass
def test_subtract(): ← Находит
pass
def helper_function(): ← Игнорирует (нет префикса test_)
passПроверка: что pytest нашёл
# Покажет список всех найденных тестов БЕЗ запуска
pytest --collect-onlyРезультат:
<Module tests/test_calculator.py>
<Function test_add>
<Function test_subtract>
<Function test_multiply>Организация больших проектов (бонус)
Для сложных проектов:
pytest-tutorial/
├── src/
│ ├── user/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── services.py
│ └── auth/
│ ├── __init__.py
│ └── handlers.py
├── tests/
│ ├── user/
│ │ ├── test_models.py
│ │ └── test_services.py
│ └── auth/
│ └── test_handlers.py
└── pytest.iniЗеркальная структура: tests/ повторяет структуру src/ для удобства навигации.
Типичные ошибки
Ошибка #1: ModuleNotFoundError
# tests/test_calculator.py
from calculator import add # ❌ ПЛОХООшибка:
ModuleNotFoundError: No module named 'calculator'Исправление:
from src.calculator import add # ✅ ХОРОШООшибка #2: Тесты не в tests/
my-project/
├── src/
└── my_tests/ ← ❌ pytest не найдёт (неправильное имя)
└── test_user.pyИсправление: Переименуйте my_tests/ в tests/.
Ошибка #3: Забыли __init__.py (Python < 3.3)
Для старых проектов может потребоваться:
touch src/__init__.py
touch tests/__init__.pyНо для современных проектов (Python 3.3+) это не обязательно!
Что вы изучили
- Создали
tests/директорию для организации файлов - Импортировали код из
src/в тесты - Поняли правила именования
test_*.pyиdef test_*() - Узнали про pytest discovery — автоматический поиск тестов
- Запускали тесты разными способами (все, один файл, один тест)
Следующий урок
Отлично! Теперь вы знаете где писать тесты. Но как тестировать классы? Как проверять исключения (ошибки)?
Переходите к уроку 2: Тестируем классы и ошибки
В следующем уроке вы узнаете:
- Как тестировать методы классов
- Как проверять что код выбрасывает исключения (
pytest.raises) - Как писать тесты для edge cases
Устранение неисправностей
Запускайте pytest из корня проекта и используйте правильный импорт:from src.calculator import add
Проверьте:
- Вы в корне проекта (`pytest-tutorial/`)
- Импорт пишется как `from src.calculator import add`
Проверьте именование файлов и функций, чтобы pytest их обнаружил
Проверьте:
- Файл: `test_*.py` или `*_test.py`
- Функция: `def test_*()`
- Проверьте сбор тестов: `pytest --collect-only`
Убедитесь, что запускаете pytest из правильной директории
Проверьте:
- Проверьте `pwd`: вы должны быть в корне проекта
- Файлы названы по паттерну `test_*.py` или `*_test.py`
Для старых версий Python добавьте пустой __init__.py в директорию src/