Основной принцип - не тестируем то что ужеткстировали и не тестируем очивидную логику
1. Не тестируем то, что можно протестировать BDD-тестами
BDD покрывает:
- ✅ Пользовательские сценарии (создал платеж → получил результат)
- ✅ HTTP-эндпоинты (GET/POST запросы)
- ✅ Интеграцию между компонентами
- ✅ Бизнес-требования в терминах пользователя
Unit-тесты покрывают:
- ✅ Внутреннюю логику, которую не видно извне
- ✅ Асинхронные задачи (tasks, workers)
- ✅ Сигналы и хуки
- ✅ Утилиты, экспортеры, сервисы
2. Не тестируем очевидную и простую логику
Не тестируем:
- ❌ Стандартное поведение Django/фреймворка
auto_now_addдля датdefault=для полей моделиon_delete=CASCADE- Индексы БД (это гарантирует Django)
- ❌ Геттеры/сеттеры без логики
- ❌ Простые присваивания атрибутов
- ❌ Строковое представление без сложной логики
Тестируем:
- ✅ Кастомную логику в
save(),clean() - ✅ Валидацию с бизнес-правилами
- ✅ Вычисляемые свойства с условиями
3. Тестируем только критически важную или сложную логику
Критичные блоки (приоритет 🔴):
- 🔴 Финансовые операции (суммы, комиссии, конвертация)
- 🔴 Транзакции и блокировки (
select_for_update) - 🔴 Асинхронные задачи (потеря данных = инцидент)
- 🔴 Сигналы с побочными эффектами
- 🔴 Интеграции с внешними системами (API, blockchain)
- 🔴 Безопасность (проверка прав, валидация токенов)
Сложные блоки (приоритет 🟡):
- 🟡 Агрегация данных с оптимизацией
- 🟡 Кэширование с инвалидацией
- 🟡 Сложные условия и ветвления
- 🟡 Регулярные выражения и парсинг
Не критичные (приоритет 🟢):
- 🟢 CRUD без бизнес-логики
- 🟢 Простые фильтры и сортировки
- 🟢 Отображение данных (форматирование)
📋 Чеклист перед написанием теста
Вопросы для самопроверки
- Этот тест можно заменить BDD-сценарием?
- Если да → пишем BDD, не unit
- Если нет → продолжаем
- Это стандартное поведение фреймворка?
- Если да → не тестируем
- Если нет → продолжаем
- Ошибка в этом коде стоит денег/данных?
- Если да → тестируем обязательно
- Если нет → оцениваем сложность
- Здесь есть сложная логика (условия, циклы, расчёты)?
- Если да → тестируем
- Если нет → возможно, не нужно
- Этот код уже покрыт другими тестами?
- Если да → не дублируем
- Если нет → продолжаем
🧪 Структура теста
Хороший тест
def test_confirm_payment_success(self):
"""Тест: успешное подтверждение платежа."""
# Arrange: подготовка данных
payment = PaymentInfo.objects.create(
account=self.account,
confirmed=False,
status="pending",
)
# Act: вызов тестируемой логики
result = process_blockchain_event(payment.id.int)
# Assert: проверка результата
payment.refresh_from_db()
assert result == f"Payment {payment.id} confirmed."
assert payment.confirmed is True
assert payment.status == "success"
Плохой тест (избыточный)
def test_id_is_uuid(self):
"""Тест: id имеет тип UUID."""
# ❌ Django гарантирует это автоматически
payment = PaymentInfo.objects.create(account=self.account)
assert isinstance(payment.id, uuid.UUID)
def test_default_confirmed_is_false(self):
"""Тест: confirmed по умолчанию False."""
# ❌ Проверяет default= в модели — это гарантирует Django
payment = PaymentInfo.objects.create(account=self.account)
assert payment.confirmed is False
📁 Организация тестов
Структура папок
payment/
├── tests/
│ ├── __init__.py # Просто docstring, без __all__
│ ├── test_tasks.py # Критично: асинхронные задачи
│ ├── test_signals.py # Критично: сигналы
│ ├── test_exporters.py # Важно: экспорт метрик
│ ├── test_middleware.py # Средне: middleware
│ └── test_models.py # Дополнение: бизнес-логика моделей
├── tasks.py
├── signals.py
└── models.py
Именование
- ✅
test_<функция>_<сценарий>.py→test_tasks_process_blockchain_event.py - ✅ Класс:
<Функция>Test→ProcessBlockchainEventTest - ✅ Метод:
test_<сценарий>_<ожидаемое>→test_confirm_payment_success
🔍 Что покрывать тестами (примеры)
✅ Покрыть тестами
| Блок | Почему | Приоритет |
|---|---|---|
tasks.py |
Асинхронная задача, потеря = инцидент | 🔴 |
signals.py |
Скрытая логика, не видна в BDD | 🔴 |
exporters.py |
Агрегация с оптимизацией (один запрос) | 🟡 |
middleware.py |
Regex,边界 cases | 🟢 |
models.save() |
Кастомная логика в save() | 🟡 |
validators.py |
Бизнес-валидация | 🟡 |
❌ Не покрывать тестами
| Блок | Почему |
|---|---|
models.id = UUIDField(...) |
Гарантирует Django |
models.created_at = auto_now_add |
Гарантирует Django |
models ForeignKey(on_delete=CASCADE) |
Гарантирует Django |
models.status = CharField(choices=...) |
Чоисы валидирует Django |
| Простые геттеры без логики | Нет бизнес-логики |
🎯 Критерии качества теста
Хороший тест
- Изолированный — не зависит от других тестов
- Детерминированный — одинаковый результат при каждом запуске
- Быстрый — < 100ms на тест
- Читаемый — понятно, что тестирует и почему
- Минимальный — проверяет одно поведение
- С mocking — внешние зависимости замоканы
Плохой тест
- Зависит от порядка запуска
- Использует реальные внешние сервисы
- Проверяет несколько вещей одновременно
- Требует долгих вычислений
- Дублирует другой тест