← Назад к вопросам

Как тестировать БД, если она не поддерживает транзакции?

2.3 Middle🔥 121 комментариев
#Базы данных (NoSQL)#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Стратегии тестирования БД без поддержки транзакций

Когда база данных не поддерживает транзакции (например, SQLite в режиме по умолчанию, или NoSQL базы вроде MongoDB), стандартный подход с rollback в конце тестов становится невозможным. Однако существует несколько эффективных стратегий изоляции тестов.

1. Использование отдельной БД для каждого теста

Этот подход гарантирует полную изоляцию тестов. Каждый тест работает с чистой копией БД:

import tempfile
import shutil
from pathlib import Path
import pytest

class DatabaseManager:
    def __init__(self):
        self.test_dirs = []
    
    def create_test_db(self, db_path="/tmp/test_db"):
        """Создаёт новую директорию БД для теста"""
        test_dir = tempfile.mkdtemp(prefix="test_db_")
        self.test_dirs.append(test_dir)
        return test_dir
    
    def cleanup(self):
        """Удаляет все временные БД"""
        for test_dir in self.test_dirs:
            if Path(test_dir).exists():
                shutil.rmtree(test_dir)

@pytest.fixture
def db_manager():
    manager = DatabaseManager()
    yield manager
    manager.cleanup()

@pytest.fixture
def test_db(db_manager):
    return db_manager.create_test_db()

def test_insert_data(test_db):
    # Каждый тест получает чистую БД
    db = MyDatabase(test_db)
    db.insert({"id": 1, "name": "Alice"})
    assert db.find_by_id(1)["name"] == "Alice"

2. Очистка данных между тестами (Truncate)

Для реляционных БД используем DELETE/TRUNCATE для очистки таблиц:

import pytest
from sqlalchemy import text

@pytest.fixture
def db_session(database_url):
    """Создаёт сессию и очищает таблицы после теста"""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import Session
    
    engine = create_engine(database_url)
    session = Session(engine)
    
    yield session
    
    # Очистка всех таблиц
    session.execute(text("TRUNCATE TABLE users CASCADE"))
    session.execute(text("TRUNCATE TABLE orders CASCADE"))
    session.execute(text("TRUNCATE TABLE products CASCADE"))
    session.commit()
    session.close()

def test_user_creation(db_session):
    user = User(id=1, name="Bob")
    db_session.add(user)
    db_session.commit()
    
    found_user = db_session.query(User).filter_by(id=1).first()
    assert found_user.name == "Bob"

3. Mock/In-Memory БД

Для быстрых unit-тестов используем in-memory версии БД или полные моки:

import pytest
from unittest.mock import MagicMock, patch

class MockDatabase:
    def __init__(self):
        self.data = {}
        self.counter = 0
    
    def insert(self, table, record):
        self.counter += 1
        record["id"] = self.counter
        if table not in self.data:
            self.data[table] = []
        self.data[table].append(record)
        return record
    
    def find_by_id(self, table, id):
        return next(
            (r for r in self.data.get(table, []) if r["id"] == id),
            None
        )

@pytest.fixture
def mock_db():
    return MockDatabase()

def test_user_insert(mock_db):
    user = mock_db.insert("users", {"name": "Charlie"})
    assert user["id"] == 1
    assert mock_db.find_by_id("users", 1)["name"] == "Charlie"

4. Снимки состояния (Snapshots)

Для NoSQL БД (MongoDB, DynamoDB) используем снимки и сравнение состояний:

import json
import pytest

@pytest.fixture
def mongo_db():
    """Подключается к тестовой MongoDB"""
    from pymongo import MongoClient
    client = MongoClient("mongodb://localhost:27017")
    db = client["test_db"]
    yield db
    # Очистка всех коллекций
    for collection_name in db.list_collection_names():
        db[collection_name].delete_many({})
    client.close()

def test_mongodb_insert(mongo_db):
    collection = mongo_db["users"]
    result = collection.insert_one({"name": "Diana", "age": 25})
    
    # Проверяем состояние
    found = collection.find_one({"_id": result.inserted_id})
    assert found["name"] == "Diana"

5. Docker контейнеры для каждого теста

Для интеграционных тестов поднимаем отдельный контейнер БД:

import pytest
from testcontainers.mongodb import MongoDbContainer

@pytest.fixture(scope="function")
def mongo_container():
    """Поднимает новый MongoDB контейнер для теста"""
    with MongoDbContainer() as container:
        yield container.get_connection_string()

def test_with_docker_mongo(mongo_container):
    from pymongo import MongoClient
    client = MongoClient(mongo_container)
    db = client["test"]
    
    # Тест в изолированном контейнере
    db.users.insert_one({"name": "Eve"})
    assert db.users.count_documents({}) == 1

Сравнение подходов

ПодходСкоростьНадёжностьСложностьЛучше для
Отдельная БДСредняяВысокаяСредняяИнтеграционные тесты
TruncateВысокаяВысокаяНизкаяРеляционные БД
MockОчень высокаяСредняяНизкаяUnit-тесты
DockerСредняяОчень высокаяВысокаяПолная изоляция
SnapshotsВысокаяВысокаяСредняяNoSQL БД

Рекомендуемая стратегия

  1. Unit-тесты: используй Mock для максимальной скорости
  2. Integration-тесты: Truncate + фикстуры для очистки
  3. E2E-тесты: Docker контейнеры для полной изоляции
  4. CI/CD: комбинируй все подходы — быстрые моки + медленные интеграционные тесты

Главное правило: каждый тест должен быть независим и не влиять на другие тесты. Выбирай подход в зависимости от типа тестов и требований к скорости.

Как тестировать БД, если она не поддерживает транзакции? | PrepBro