Сьогодні ми візьмемо все, що дізналися про тестування API, і застосуємо ці знання на практиці. У центрі нашої уваги опиняться тести для існуючих ендпоінтів нашого проєкту. Ми будемо писати тести для CRUD-операцій, перевіряти авторизацію та дозволи, а також відловлювати потенційні баги.
Створюємо тести для CRUD-операцій
Наша мета — протестувати базові операції CRUD для початкового API, який ми розробляли раніше. До цього моменту, у нас вже є модель Book з полями title і author, а також ендпоінти для цього ресурсу.
Пригадати: ендпоінти для моделі Book
GET /api/books/— отримати список усіх книг;POST /api/books/— створити нову книгу;GET /api/books/<id>/</id>— отримати інформацію про книгу за її ID;PUT /api/books/<id>/</id>— оновити інформацію про книгу;DELETE /api/books/<id>/</id>— видалити книгу.
Початкова підготовка Створіть файл test_views.py у директорії з тестами вашого додатка (наприклад, books/tests/). Після цього можна приступати.
Тести для списку книг (GET)
Спочатку ми перевіримо, що наш ендпоінт /api/books/ повертає коректний результат.
import pytest
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from books.models import Book
@pytest.mark.django_db
def test_get_books_list():
# Створюємо тестові дані
Book.objects.create(title="Django for Beginners", author="William S. Vincent")
Book.objects.create(title="Python Crash Course", author="Eric Matthes")
# Створюємо клієнт для тестів
client = APIClient()
# Виконуємо GET-запит
response = client.get(reverse('books-list'))
# Перевіряємо статус відповіді
assert response.status_code == status.HTTP_200_OK
# Переконуємось, що повернені дані коректні
data = response.json()
assert len(data) == 2
assert data[0]['title'] == "Django for Beginners"
assert data[1]['title'] == "Python Crash Course"
Тут ми перевіряємо, що:
- Наш ендпоінт повертає успішну відповідь з кодом
200; - Вміст відповіді відповідає нашим тестовим даним.
Тести для створення книги (POST)
Тепер давайте протестуємо, що ендпоінт для створення книги працює як слід.
@pytest.mark.django_db
def test_create_book():
# Створюємо клієнт для тестів
client = APIClient()
# Створюємо запит на додавання книги
payload = {
"title": "Two Scoops of Django",
"author": "Audrey Roy Greenfeld"
}
response = client.post(reverse('books-list'), payload, format='json')
# Перевіряємо статус відповіді
assert response.status_code == status.HTTP_201_CREATED
# Переконуємось, що книга створена в базі даних
book = Book.objects.get(id=response.data['id'])
assert book.title == payload['title']
assert book.author == payload['author']
Чистий, простий тест для додавання книги. Ми:
- Перевіряємо, що статус відповіді —
201 CREATED; - Перевіряємо, що об'єкт дійсно збережений у базі даних із вказаними полями.
Тести для отримання книги (GET /<id>/)</id>
Наступний крок — перевірити, що ми можемо отримати конкретний об'єкт за його ID.
@pytest.mark.django_db
def test_get_book_detail():
# Створюємо тестові дані
book = Book.objects.create(title="Effective Python", author="Brett Slatkin")
# Створюємо клієнт для тестів
client = APIClient()
# Виконуємо GET-запит
response = client.get(reverse('books-detail', kwargs={'pk': book.id}))
# Перевіряємо статус відповіді
assert response.status_code == status.HTTP_200_OK
# Переконуємось, що дані відповіді збігаються з даними книги
data = response.json()
assert data['title'] == book.title
assert data['author'] == book.author
Максимально детально перевіряємо, що повертаються саме ті дані, які пов'язані з конкретним ID.
Тести для оновлення книги (PUT)
Тепер перевіримо, як працює оновлення книги.
@pytest.mark.django_db
def test_update_book():
# Створюємо тестові дані
book = Book.objects.create(title="Python Tricks", author="Dan Bader")
# Створюємо клієнт для тестів
client = APIClient()
# Дані для оновлення
payload = {
"title": "Python Tricks Updated",
"author": "Dan Bader"
}
response = client.put(reverse('books-detail', kwargs={'pk': book.id}), payload, format='json')
# Перевіряємо статус відповіді
assert response.status_code == status.HTTP_200_OK
# Перевіряємо, що книга оновилась
book.refresh_from_db()
assert book.title == payload['title']
assert book.author == payload['author']
Ми тут використовуємо метод refresh_from_db, щоб оновити об'єкт із бази даних.
Тести для видалення книги (DELETE)
Нарешті, протестуємо видалення об'єкта.
@pytest.mark.django_db
def test_delete_book():
# Створюємо тестові дані
book = Book.objects.create(title="Learn Python the Hard Way", author="Zed Shaw")
# Створюємо клієнт для тестів
client = APIClient()
# Видаляємо книгу
response = client.delete(reverse('books-detail', kwargs={'pk': book.id}))
# Перевіряємо статус відповіді
assert response.status_code == status.HTTP_204_NO_CONTENT
# Переконуємось, що книга видалена з бази даних
assert not Book.objects.filter(id=book.id).exists()
Книга видаляється успішно, а ми перевіряємо, що вона більше не існує в базі даних.
Аналіз і виправлення помилок у тестах
Помилки під час тестування — це нормально. Якщо ти зіштовхнувся з багом або тест не проходить, треба діяти послідовно:
- Перевір повідомлення про помилку. Воно часто підкаже, де саме є проблема.
- Використовуй print або breakpoints для дебагу тесту.
- Запускай тести ізольовано, щоб визначити, який саме з них викликає проблему (
pytest -k test_name).
Припустимо, що ти забув зареєструвати свій ендпоінт у urls.py. Тести одразу покажуть помилку NoReverseMatch, що допоможе тобі миттєво виправити проблему.
Практика: написання тестів
Спробуйте написати тести для наступних випадків:
- Перевірка, що при відправці недопустимих даних (
titleабоauthorпорожні) наш API повертає помилку валідації з кодом400 BAD REQUEST. - Тест для перевірки, що пагінований ендпоінт повертає задану кількість об'єктів на сторінці.
- Перевірте, що у неавторизованого користувача немає доступу до створення книги (якщо у вас увімкнений такий сценарій).
На цьому все! Ви написали тести для CRUD-операцій та навчилися аналізувати результати тестування. Завдяки такому підходу ви тепер впевнені, що ваші API ендпоінти працюють коректно, а зміни в коді не ламають існуючий функціонал.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ