Ответ
Модульное (unit) тестирование API в Django фокусируется на проверке отдельных компонентов (например, сериализаторов, представлений, менеджеров) в изоляции от остальной системы.
Почему это важно? Это позволяет быстро выявлять ошибки в логике конкретных частей кода, упрощает отладку и обеспечивает высокую скорость выполнения тестов, так как не требуется полная загрузка Django-окружения или обращение к базе данных.
Основные подходы и инструменты:
unittest.TestCase(илиpytest): Для написания базовых тестовых классов и методов.- Мокирование (
unittest.mock): Ключевой элемент unit-тестирования. Позволяет заменять внешние зависимости (БД, внешние API, кэш, другие компоненты Django) на контролируемые объекты, чтобы тестировать компонент изолированно. - Тестирование сериализаторов: Прямая проверка валидации и преобразования данных, без участия HTTP-запросов.
- Тестирование функций представлений (views): Вызов функций представлений напрямую, передача
HttpRequestобъектов (часто мокированных) и проверка возвращаемогоHttpResponse.
Пример модульного теста для сериализатора:
from django.test import TestCase
from rest_framework import serializers
from datetime import date
# Пример простой модели (для демонстрации, в реальном проекте это была бы Django Model)
class MyModel:
def __init__(self, id, name, created_at):
self.id = id
self.name = name
self.created_at = created_at
# Пример сериализатора для MyModel
class MyModelSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=100, required=True)
created_at = serializers.DateField(read_only=True)
def create(self, validated_data):
# В реальном приложении здесь было бы создание объекта Django Model
return MyModel(id=1, **validated_data, created_at=date.today())
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
return instance
class MyModelSerializerTest(TestCase):
def test_serializer_validation_success(self):
"""Проверка успешной валидации данных сериализатором."""
data = {'name': 'Test Item'}
serializer = MyModelSerializer(data=data)
self.assertTrue(serializer.is_valid()) # Ожидаем успешную валидацию
self.assertEqual(serializer.validated_data['name'], 'Test Item')
def test_serializer_validation_failure_empty_name(self):
"""Проверка валидации при пустом имени."""
data = {'name': ''}
serializer = MyModelSerializer(data=data)
self.assertFalse(serializer.is_valid()) # Ожидаем ошибку валидации
self.assertIn('name', serializer.errors) # Проверяем наличие ошибки для поля 'name'
def test_serializer_create_method(self):
"""Проверка метода create сериализатора."""
data = {'name': 'New Item'}
serializer = MyModelSerializer(data=data)
serializer.is_valid(raise_exception=True)
instance = serializer.save() # Вызываем метод create
self.assertIsInstance(instance, MyModel)
self.assertEqual(instance.name, 'New Item')
self.assertEqual(instance.created_at, date.today())
Ключевые моменты модульного тестирования API:
- Фокус на изоляции: Тестируется один компонент за раз, минимизируя зависимости от других частей системы.
- Активное использование
unittest.mock: Для имитации поведения базы данных, внешних API, кэша и других зависимостей, чтобы избежать реальных взаимодействий и обеспечить предсказуемость тестов. - Проверка внутренней логики: Тесты сосредоточены на корректности алгоритмов, валидации данных и обработке ошибок внутри конкретного компонента.
- Различие с интеграционными тестами: В отличие от интеграционных тестов,
APITestCaseизrest_framework.testобычно не используется для чистого модульного тестирования, так как он имитирует полный HTTP-запрос и ответ, что выходит за рамки изоляции компонента.
Ответ 18+ 🔞
Давай я тебе на пальцах, но с матом, объясню, что за зверь такой — модульное тестирование API в Джанге. Представь, что у тебя есть бутерброд. Ты же не будешь его целиком в рот пихать, чтобы проверить, свежий ли хлеб? Ты отломишь кусочек, посмотришь, понюхаешь, может, даже на вкус попробуешь. Вот это и есть unit-тестирование, блядь. Берёшь один ингредиент — сериализатор, вьюху, менеджер — и проверяешь его на вшивость отдельно от всей этой кухни.
А нахуя это надо? Да чтобы не было потом, как в том анекдоте: "А компот?" — "А компот говно!". Когда всё сломается в продакшене, искать причину будет пиздец как долго. А так ты каждый кусочек кода проверил по отдельности, и если где-то говно, ты сразу знаешь, в каком именно холодильнике оно протухло. И тесты эти быстрые, потому что не надо поднимать всю эту бандуру — базу данных, сервер, кэш. Просто взял и проверил.
Чем этим занимаются?
unittest.TestCaseилиpytest— это как твои рабочие инструменты. Молоток и отвёртка, чтобы собирать эти тесты.- Мокирование (
unittest.mock) — вот это, сука, главный фокус! Это как подсадная утка. Надо проверить функцию, которая лезет в базу? А мы на её место подсуем муляж базы, который всегда говорит "всё ок" или "всё хуёво", как нам надо. Полная изоляция, никаких случайных пиздецов из-за реальной БД. - Тестирование сериализаторов — просто суём в него данные и смотрим, не обосрётся ли он на валидации, правильно ли преобразует.
- Тестирование вьюх — вызываем функцию представления напрямую, как обычную питонячью функцию, подсовывая ей сфабрикованный запрос. Никаких тебе
runserverи постукиваний по урлам.
Смотри, как это выглядит на практике, ёпта:
from django.test import TestCase
from rest_framework import serializers
from datetime import date
# Допустим, у нас есть какая-то модель. Для примера, чтоб не заморачиваться.
class MyModel:
def __init__(self, id, name, created_at):
self.id = id
self.name = name
self.created_at = created_at
# А это её сериализатор, который мы будем пытать.
class MyModelSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=100, required=True)
created_at = serializers.DateField(read_only=True)
def create(self, validated_data):
# Тут в реальности был бы ORM и запись в БД, но нам похуй для примера.
return MyModel(id=1, **validated_data, created_at=date.today())
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
return instance
class MyModelSerializerTest(TestCase):
def test_serializer_validation_success(self):
"""Проверяем, что сериализатор не дурак и нормальные данные проглатывает."""
data = {'name': 'Test Item'}
serializer = MyModelSerializer(data=data)
self.assertTrue(serializer.is_valid()) # Ожидаем, что всё заебись
self.assertEqual(serializer.validated_data['name'], 'Test Item') # И имя на месте
def test_serializer_validation_failure_empty_name(self):
"""А тут проверяем, что если нам подсунуть пустое имя, он начнёт материться."""
data = {'name': ''}
serializer = MyModelSerializer(data=data)
self.assertFalse(serializer.is_valid()) # Ожидаем, что валидация провалится
self.assertIn('name', serializer.errors) # И ошибка будет именно в поле 'name'
def test_serializer_create_method(self):
"""А теперь смотрим, как он создаёт объекты. Не хуйню же он делает там?"""
data = {'name': 'New Item'}
serializer = MyModelSerializer(data=data)
serializer.is_valid(raise_exception=True)
instance = serializer.save() # Вызываем тот самый метод create
self.assertIsInstance(instance, MyModel) # Получили ли мы объект?
self.assertEqual(instance.name, 'New Item') # Имя совпадает?
self.assertEqual(instance.created_at, date.today()) # Дату сегодняшнюю подставил?
Итоговая мысль, блядь:
- Изоляция — наше всё. Один компонент, один тест. Никаких соседей, которые могут нагадить.
unittest.mock— твой лучший друг. Хочешь протестировать логику, а не работу базы? Замокай её нахуй! И живёшь спокойно.- Логика, а не церемонии. Нам важно, что внутри функции происходит, а не то, как она выглядит со стороны HTTP.
- Забудь про
APITestCaseдля этого. Он для интеграционных тестов, где надо эмулировать полный запрос. А тут мы хирургическим путём, точечно, блядь. В рот меня чих-пых, понимаешь разницу?