Python · Тестирование с pytest · Экспертный
Тестирование Django-приложений с pytest-django
Тестируйте Django views, модели и формы с помощью встроенных фикстур pytest-django.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Паттерны тестирования Django на практике
#Справочник pytest-django
#Упражнения:
Проверить что Django-представление возвращает HTTP 200
#У вас есть Django-представление `task_list` по URL `/tasks/`, возвращающее JSON-ответ с ключом `results`, содержащим список задач. Настройте `pytest.ini` с `DJANGO_SETTINGS_MODULE` и напишите тест, использующий встроенную фикстуру `client`, который: 1. Вызывает `GET /tasks/` 2. Проверяет что статус-код равен 200 3. Проверяет что JSON ответа содержит ключ `results` 4. Проверяет что `results` является списком
# pytest.ini
# [pytest]
# DJANGO_SETTINGS_MODULE = myproject.settings
# tests/test_views.py
import pytest
@pytest.mark.django_db
def test_task_list_returns_200(client):
# вызвать GET /tasks/ и проверить статус + структуру
pass
Решение
# pytest.ini
# [pytest]
# DJANGO_SETTINGS_MODULE = myproject.settings
# tests/test_views.py
import pytest
@pytest.mark.django_db
def test_task_list_returns_200(client):
response = client.get('/tasks/')
assert response.status_code == 200
data = response.json()
assert 'results' in data
assert isinstance(data['results'], list)
Проверить сохранение модели и персистентность в базе данных
#У вас есть модель `Task` с полями `title` (CharField) и `completed` (BooleanField, default=False). Напишите два теста: 1. **test_default_completed_is_false** -- создайте `Task`, затем получите его из базы данных по первичному ключу и проверьте что `completed` равен `False`. 2. **test_task_can_be_completed** -- создайте `Task` с `completed=False`, установите `completed=True`, вызовите `.save()`, затем вызовите `.refresh_from_db()` и проверьте что значение сохранилось.
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_default_completed_is_false():
# создать и получить свежий из БД
pass
@pytest.mark.django_db
def test_task_can_be_completed():
# создать, обновить, сохранить, обновить из БД, проверить
pass
Решение
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_default_completed_is_false():
task = Task.objects.create(title='New task')
task_from_db = Task.objects.get(pk=task.pk)
assert task_from_db.completed is False
@pytest.mark.django_db
def test_task_can_be_completed():
task = Task.objects.create(title='Finish report', completed=False)
task.completed = True
task.save()
task.refresh_from_db()
assert task.completed is True
Проверить аутентифицированное представление с force_login
#У вас есть представление `my_tasks` по `/my-tasks/`, декорированное `@login_required`. Оно возвращает только задачи, принадлежащие аутентифицированному пользователю, в формате JSON `{'results': [...]}`. Напишите два теста: 1. **test_my_tasks_redirects_anonymous** -- проверьте что неаутентифицированный GET возвращает 302. 2. **test_my_tasks_returns_only_owners_tasks** -- создайте двух пользователей и по одной задаче для каждого. Войдите как `user1` через `client.force_login()`. Проверьте статус 200 и что ответ содержит ровно одну задачу, принадлежащую `user1`.
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_my_tasks_redirects_anonymous(client):
pass
@pytest.mark.django_db
def test_my_tasks_returns_only_owners_tasks(client, django_user_model):
pass
Решение
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_my_tasks_redirects_anonymous(client):
response = client.get('/my-tasks/')
assert response.status_code == 302
@pytest.mark.django_db
def test_my_tasks_returns_only_owners_tasks(client, django_user_model):
alice = django_user_model.objects.create_user(username='alice', password='x')
bob = django_user_model.objects.create_user(username='bob', password='x')
Task.objects.create(title="Alice's task", owner=alice)
Task.objects.create(title="Bob's task", owner=bob)
client.force_login(alice)
response = client.get('/my-tasks/')
assert response.status_code == 200
results = response.json()['results']
assert len(results) == 1
assert results[0]['title'] == "Alice's task"
Переопределить настройку Django для одного теста с фикстурой settings
#У вас есть вспомогательная функция: ```python # myapp/utils.py from django.conf import settings def get_max_tasks(): return getattr(settings, 'MAX_TASKS_PER_USER', 10) ``` Напишите два теста: 1. **test_get_max_tasks_with_override** -- используйте фикстуру `settings` для установки `MAX_TASKS_PER_USER = 3` и проверьте что `get_max_tasks()` возвращает `3`. 2. **test_get_max_tasks_default** -- не используйте фикстуру `settings`; проверьте что `get_max_tasks()` возвращает дефолтное значение `10`. Оба теста должны проходить независимо от порядка выполнения.
# tests/test_utils.py
import pytest
from myapp.utils import get_max_tasks
def test_get_max_tasks_with_override(settings):
pass
def test_get_max_tasks_default():
pass
Решение
# tests/test_utils.py
import pytest
from myapp.utils import get_max_tasks
def test_get_max_tasks_with_override(settings):
settings.MAX_TASKS_PER_USER = 3
assert get_max_tasks() == 3
def test_get_max_tasks_default():
assert get_max_tasks() == 10
Проверить функцию представления напрямую с RequestFactory
#У вас есть функция представления `task_list` (не URL -- сама функция). Напишите тест, использующий фикстуру `rf` (RequestFactory), который: 1. Создаёт два объекта `Task` в базе данных. 2. Строит GET-запрос через `rf.get('/tasks/')`. 3. Вызывает функцию представления напрямую: `response = task_list(request)`. 4. Проверяет статус 200 и что JSON-ответ содержит ровно 2 задачи. Затем напишите второй тест для аутентифицированного представления `my_tasks`. Поскольку `rf` пропускает промежуточное ПО, `request.user` не устанавливается автоматически -- назначьте его вручную.
import json
import pytest
from myapp.views import task_list, my_tasks
from myapp.models import Task
@pytest.mark.django_db
def test_task_list_with_rf(rf):
pass
@pytest.mark.django_db
def test_my_tasks_with_rf(rf, django_user_model):
pass
Решение
import json
import pytest
from myapp.views import task_list, my_tasks
from myapp.models import Task
@pytest.mark.django_db
def test_task_list_with_rf(rf):
Task.objects.create(title='First')
Task.objects.create(title='Second')
request = rf.get('/tasks/')
response = task_list(request)
assert response.status_code == 200
data = json.loads(response.content)
assert len(data['results']) == 2
@pytest.mark.django_db
def test_my_tasks_with_rf(rf, django_user_model):
user = django_user_model.objects.create_user(username='alice', password='x')
Task.objects.create(title="Alice's task", owner=user)
request = rf.get('/my-tasks/')
request.user = user
response = my_tasks(request)
assert response.status_code == 200
data = json.loads(response.content)
assert len(data['results']) == 1