Мы не решаемся на трудные дела не потому, что они трудны. Они трудны не потому, что мы не решаемся на них решиться. — Сенека
Раздел документации 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. Если вы заметили ошибку, не стесняйтесь оставить комментарий.