Признайтесь, многие считают тестирование скучной рутиной. Но на самом деле это наш надежный щит от катастроф! Представьте, что ваш код — это океанский лайнер, а тесты — команда внимательных инженеров, которые постоянно проверяют, не начал ли он где-то протекать. CRUD-операции находятся в самом сердце любого приложения, и сбои в их работе могут обернуться настоящей катастрофой: от потери важных данных до полного краха бизнес-логики.
Тестирование CRUD-операций становится не просто полезным, а необходимым по нескольким причинам:
- Проверка корректности работы API: только с помощью тестов можно быть по-настоящему уверенным, что ваш код правильно выполняет базовые запросы
Create, Read, Update, Delete. - Предотвращение багов: ошибки в работе с базой данных особенно коварны — хорошие тесты действуют как страховой полис от самых неприятных сюрпризов.
- Спокойствие при расширении: когда вы добавляете новые фичи, работающие тесты дают уверенность, что вы не сломали уже существующий функционал.
Подходы к тестированию
- Тестирование на уровне базы данных.
На этом уровне мы проверяем, что SQLAlchemy выполняет операции с базой данных корректно. Например, что модель сохраняется правильно, данные читаются так, как мы ожидаем, и никакие уникальности или ограничения на уровне базы не нарушаются. - Тестирование API.
Если ваше приложение предоставляет REST API, то мы тестируем конечные точки (endpoints) приложения — проверяем, как они обрабатывают запросы и возвращают ответы. Это важно для проверки работы приложения "снаружи". - Интеграционное тестирование.
Такое тестирование охватывает более крупные компоненты системы. Например, вы можете проверить, что вызов конкретного API в FastAPI действительно создает запись в базе через SQLAlchemy.
Написание тестов для CRUD-операций: практика
Давайте разберем, как это всё выглядит в коде! Для этого мы повторно используем наше приложение на FastAPI, которое было создано в предыдущих лекциях.
Перед началом тестирования нужно подготовить тестовое окружение. Мы будем использовать библиотеку Pytest, а для тестирования базы данных создадим специальную тестовую базу.
Установим зависимости для тестов:
pip install pytest pytest-asyncio
Если вы используете PostgreSQL или любую другую СУБД, дополнительно установите драйверы, необходимые для подключения, например:
pip install psycopg2
Пример тестирования CRUD-операций в FastAPI
Для начала создадим самую простую структуру приложения. Пусть это будет API для управления пользователями.
Модель пользователя
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
email = Column(String, unique=True, nullable=False)
CRUD-операции
Создадим файл crud.py с основными функциями для взаимодействия с базой.
from sqlalchemy.orm import Session
from .models import User
def create_user(db: Session, name: str, email: str):
user = User(name=name, email=email)
db.add(user)
db.commit()
db.refresh(user)
return user
def get_user(db: Session, user_id: int):
return db.query(User).filter(User.id == user_id).first()
def update_user(db: Session, user_id: int, name: str):
user = db.query(User).filter(User.id == user_id).first()
if user:
user.name = name
db.commit()
db.refresh(user)
return user
def delete_user(db: Session, user_id: int):
user = db.query(User).filter(User.id == user_id).first()
if user:
db.delete(user)
db.commit()
return user
Тестирование CRUD операций
Теперь перейдем к тестам. Создадим файл test_crud.py.
Создадим временную базу данных в памяти (SQLite) для обеспечения изоляции тестов.
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ..models import Base
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="function")
def db():
Base.metadata.create_all(bind=engine)
session = TestingSessionLocal()
try:
yield session
finally:
session.close()
Тесты CREATE и READ. Тестируем создание записи и чтение этой же записи.
from ..crud import create_user, get_user
def test_create_user(db):
user = create_user(db, name="John Doe", email="john.doe@example.com")
assert user.id is not None
assert user.name == "John Doe"
assert user.email == "john.doe@example.com"
def test_get_user(db):
user = create_user(db, name="John Doe", email="john.doe@example.com")
fetched_user = get_user(db, user.id)
assert fetched_user == user
Тесты UPDATE и DELETE. Обновим имя пользователя и удалим его.
from ..crud import create_user, update_user, delete_user, get_user
def test_update_user(db):
user = create_user(db, name="John Doe", email="john.doe@example.com")
updated_user = update_user(db, user.id, name="Jane Doe")
assert updated_user.name == "Jane Doe"
def test_delete_user(db):
user = create_user(db, name="John Doe", email="john.doe@example.com")
delete_user(db, user.id)
assert get_user(db, user.id) is None
Пример тестирования через API FastAPI
Давайте теперь протестируем API, которое использует эти CRUD-функции.
Для тестирования FastAPI приложений используется стандартный TestClient из библиотеки FastAPI.
from fastapi.testclient import TestClient
from ..main import app
client = TestClient(app)
def test_create_user_api():
response = client.post("/users/", json={"name": "John Doe", "email": "john.doe@example.com"})
assert response.status_code == 200
assert response.json()["name"] == "John Doe"
Тестирование в Django
Если вместо FastAPI вы используете Django, процесс будет выглядеть чуть иначе. Django предоставляет встроенный TestCase для работы с тестами. Вот пример:
from django.test import TestCase
from .models import User
class UserCRUDTests(TestCase):
def test_create_user(self):
user = User.objects.create(name="John Doe", email="john.doe@example.com")
self.assertIsNotNone(user.id)
self.assertEqual(user.name, "John Doe")
self.assertEqual(user.email, "john.doe@example.com")
def test_update_user(self):
user = User.objects.create(name="John Doe", email="john.doe@example.com")
user.name = "Jane Doe"
user.save()
self.assertEqual(user.name, "Jane Doe")
def test_delete_user(self):
user = User.objects.create(name="John Doe", email="john.doe@example.com")
user_id = user.id
user.delete()
self.assertFalse(User.objects.filter(id=user_id).exists())
Полезные советы
- Фикстуры для изоляции: используйте фикстуры Pytest или Django для создания тестовых данных. Это поможет вам избежать нежелательного загрязнения тестовой среды.
- Mock-тесты: если вы тестируете функции, которые взаимодействуют с внешними API/сервисами, используйте
unittest.mock. - Интеграция в CI/CD: автоматизируйте запуск тестов в своем CI/CD процессе, чтобы сразу выявлять ошибки.
pytest --cov=your_project/
Теперь вы можете быть уверены, что ваши CRUD операции работают стабильно, независимо от среды и изменений в коде!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ