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