Тестирование с помощью Django REST Framework


Мы не решаемся на трудные дела не потому, что они трудны. Они трудны не потому, что мы не решаемся на них решиться. — Сенека

Раздел документации Django REST framework (DRF) посвящен тестированию ваших представлений API. В нем есть много информации о различных инструментах, которые предоставляет библиотека, и примеры кода, как их использовать. Меня спросили, как протестировать вызов API, имитируя запрос, поэтому вот как я обычно делаю это в своих проектах.

Сериализатор

Во-первых, у нас есть базовая модель для дома, поля которой описывают его адрес. BaseModel — это абстрактный класс, который добавляет поля UUID, created_at и modified_at к моим объектам:

class Home(BaseModel):
    class SupportedCountries:
        CAN = "CANADA"
        US = "UNITED_STATES"

        CHOICES = (
            (CAN, _("Canada")),
            (US, _("United States")),
        )

    name = models.CharField(max_length=50, default="Home")
    address_line_1 = models.CharField(max_length=500)
    address_line_2 = models.CharField(
        max_length=500, blank=True, verbose_name=_("Address line 2 (optional)")
    )
    city = models.CharField(max_length=100)
    state_province = models.CharField(max_length=50, verbose_name=_("State/Province"))
    zip_code_postal_code = models.CharField(
        max_length=7, verbose_name=_("Zip code/Postal code")
    )
    country = models.CharField(
        max_length=15,
        choices=SupportedCountries.CHOICES,
        default=SupportedCountries.US,
    )

    def __str__(self):
        return self.name
Войти в полноэкранный режим Выйти из полноэкранного режима

Таким образом, результирующий сериализатор очень прост и не имеет внутреннего сериализатора. Мы объявляем все поля из модели плюс поле id из абстрактного класса BaseModel:

class HomeSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Home
        fields = [
            "id",
            "name",
            "address_line_1",
            "address_line_2",
            "city",
            "state_province",
            "zip_code_postal_code",
            "country",
        ]
Вход в полноэкранный режим Выход из полноэкранного режима

Тестовый класс

Для создания объектов на основе моих моделей для моих тестов я использую фабрику boy и заполняю поля поддельными данными с помощью faker:

import factory
from factory.django import DjangoModelFactory


class HomeFactory(DjangoModelFactory):
    name = "Home"
    address_line_1 = factory.Faker("street_address")
    address_line_2 = factory.Faker("building_number")
    city = factory.Faker("city")
    state_province = factory.Faker("state")
    zip_code_postal_code = factory.Faker("postcode")
    country = models.Home.SupportedCountries.US

    class Meta:
        model = models.Home
Войти в полноэкранный режим Выход из полноэкранного режима

Для тестирования я обычно использую класс Django TestCase, который является подклассом Python unittest.TestCase. В нем есть клиент для аутентификации пользователя, чтобы вы могли протестировать миксины и поведение разрешений. В DRF есть класс APITestCase, который расширяет Django TestCase с теми же функциями, но с использованием APIClient, который позволяет аутентифицироваться как пользователь API.

class TestHomeAPIView(APITestCase):
    def setUp(self):
        self.url = reverse("api:home_view") # use the view url
        self.home = factories.HomeFactory()
        user = factories.UserFactory()
        self.client.force_authenticate(user=user)

    def test_get(self):
        response = self.client.get(self.url)
        response.render()
        self.assertEquals(200, response.status_code)
        expected_content = {
                "id": str(self.home.id),
                "address_line_1": self.home.address_line_1,
                "address_line_2": self.home.address_line_2,
                "city": str(self.home.city),
                "state_province": str(self.home.state_province),
                "zip_code_postal_code": str(self.home.zip_code_postal_code),
                "country": str(self.home.country),
            }
        self.assertListEqual(expected_content, json.loads(response.content))
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте погрузимся в этот тестовый код и объясним, что он делает. Сначала у нас есть функция setUp, которая является общей для TestCase и определяет код, который будет выполняться перед каждой функцией с префиксом test_ в классе. Я определяю url для моего представления и объект home, который будет создаваться каждый раз заново, что делает мой тест детерминированным. Затем я создаю пользователя с фабрикой, которая дает разрешения, необходимые для тестирования представления. Последняя строка действительно важна, даже более важна для API представления с механизмами аутентификации и разрешений. По умолчанию массив DEFAULT_PERMISSION_CLASSES содержит rest_framework.permissions.IsAuthenticated, поэтому все мои представления требуют входа в систему в качестве пользователя. Мы аутентифицируем пользователя с помощью APIClient, чтобы мы могли делать вызовы к представлению и оценивать разрешения.

Затем я объявляю test_get, чтобы сделать простой GET тест моего представления. Сначала я фиксирую ответ на вызов в одноименной переменной. Сначала я предполагал, что response.content будет доступен, но из-за внутреннего механизма рендеринга шаблонов в Django это не так. Нам нужно вручную объявить рендеринг. Затем я утверждаю, что мой ответ был успешным и генерирую HTTP 200, что означает, что все прошло нормально. Следующая строка объявляет переменную expected_content, которая представляет собой dict, содержащий все поля моего объекта home. Наконец, я разбираю объект JSON на объект Python с помощью json.loads, который я сравниваю с ожидаемым содержимым, чтобы убедиться, что то, что я получил, правильно.

Подведение итогов

Django REST Framework имеет отличную документацию с подробным разделом о тестах. Мы рассмотрели несколько фрагментов кода для модели, сериализатора и, наконец, тест. Этот тест был написан а-ля Django с использованием factory boy и Faker, а также с использованием DRF APIClient, содержащегося в классе тестирования APITestCase.

Мы прошли от начала до конца мой процесс тестирования представления API. Возможно, некоторые части можно было бы оптимизировать, но для меня это оказалось надежным.

Пожалуйста, поделитесь со мной любыми советами или удивительными кусочками кода, которые облегчают ваш опыт тестирования с Django REST Framework. Если вы заметили ошибку, не стесняйтесь оставить комментарий.

Оцените статью
devanswers.ru
Добавить комментарий