Признайтеся, багато хто вважає тестування нудною рутиною. Але насправді це наш надійний щит від катастроф! Уявіть, що ваш код — це океанський лайнер, а тести — команда уважних інженерів, які постійно перевіряють, чи не почав він десь протікати. 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-операції працюють стабільно, незалежно від оточення та змін у коді!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ