Как протестировать API эндпоинт, который извлекает данные из базы данных

«Как протестировать API эндпоинт, который извлекает данные из базы данных» — вопрос из категории Тестирование, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Тестирование такого эндпоинта можно разделить на два основных подхода: модульное (unit) тестирование с изоляцией БД и интеграционное тестирование с реальной тестовой базой данных.

1. Модульный тест с использованием Mock-объектов

Этот подход изолирует слой API (view, controller) от базы данных. Мы заменяем вызов к БД "заглушкой" (mock), которая возвращает предопределенные данные. Это делает тесты очень быстрыми и независимыми от состояния БД.

Цель: Проверить логику эндпоинта: обработку запроса, сериализацию данных и формирование HTTP-ответа.

Пример с unittest.mock:

from unittest.mock import patch
from flask import Flask

# Предположим, это наш код приложения
app = Flask(__name__)

def get_users_from_db():
    # ... здесь реальный код для запроса к БД
    pass

@app.route('/api/users')
def get_users():
    users = get_users_from_db()
    return {'data': users}

# Тест
@patch('__main__.get_users_from_db') # Путь к мокируемой функции
def test_get_users_endpoint(mock_get_users):
    # Arrange: настраиваем mock
    mock_return_data = [{'id': 1, 'name': 'Alice'}]
    mock_get_users.return_value = mock_return_data

    # Act: делаем запрос к эндпоинту
    with app.test_client() as client:
        response = client.get('/api/users')

    # Assert: проверяем результат
    assert response.status_code == 200
    assert response.json == {'data': mock_return_data}
    mock_get_users.assert_called_once() # Убеждаемся, что функция была вызвана

2. Интеграционный тест с тестовой базой данных

Этот подход проверяет взаимодействие всех компонентов системы: HTTP-сервер, код эндпоинта, ORM и базу данных. Тесты выполняются на отдельной, изолированной тестовой БД, которая создается и очищается для каждого тестового запуска.

Цель: Убедиться, что SQL-запросы корректны, ORM-маппинг работает, и вся цепочка от запроса до ответа функционирует как единое целое.

Пример с pytest и pytest-django:

import pytest
from django.urls import reverse
from rest_framework.test import APIClient

from myapp.models import User

@pytest.mark.django_db
def test_get_users_integration():
    # Arrange: создаем реальные данные в тестовой БД
    User.objects.create(username='bob', email='bob@example.com')
    client = APIClient()
    url = reverse('user-list') # Получаем URL эндпоинта

    # Act: делаем запрос
    response = client.get(url)

    # Assert: проверяем реальный ответ, сформированный на основе данных из БД
    assert response.status_code == 200
    assert response.json() == [
        {
            'username': 'bob',
            'email': 'bob@example.com'
        }
    ]

Какой подход выбрать?

  • Модульные тесты идеальны для сложной бизнес-логики внутри эндпоинта. Они быстрые.
  • Интеграционные тесты необходимы для проверки корректности работы с БД и являются более надежным показателем работоспособности фичи. Обычно их пишут для ключевых сценариев использования.