Короткий ответ
Нужен тест, который на тестовой БД применяет миграции до нужной ревизии, проверяет итоговую схему (и, при изменении данных миграцией, их корректность), а затем откатывает миграцию назад и проверяет, что схема вернулась в исходное состояние без потери важных данных.
Как это работает подробнее
Инструменты миграций (Alembic для SQLAlchemy, стандартные миграции Django) предоставляют программный интерфейс для апгрейда и даунгрейда до конкретной ревизии; тест может вызвать upgrade('head') и downgrade('-1') на изолированной тестовой БД и после каждого шага интроспектировать структуру таблиц (через information_schema или метаданные ORM).
Пример кода
testing_example_321.py
def test_migration_adds_not_null_column_with_default(alembic_config, db_connection):
seed_users_table(db_connection, rows=[{"id": 1, "name": "Ann"}])
run_migration(alembic_config, revision="+1") # применяем следующую миграцию
row = db_connection.execute("SELECT status FROM users WHERE id=1").fetchone()
assert row.status == "active" # новое поле заполнено дефолтом
run_migration(alembic_config, revision="-1") # откат
columns = get_table_columns(db_connection, "users")
assert "status" not in columnsЧто использовать на практике
- Практика. Особенно важно тестировать миграции, которые переносят или трансформируют данные (а не только меняют схему) — например, разбивают одно поле на два. Такой тест должен заранее засеять БД репрезентативными данными перед апгрейдом и проверить, что после миграции данные преобразованы верно, а после отката не потеряны безвозвратно, если это возможно по природе миграции.
Подводные камни
- Риск. Тестировать миграции только на пустой БД, из-за чего проблемы с реальными данными (NULL в новом NOT NULL поле, конфликт уникальности после трансформации) обнаруживаются только в production на реальных строках. Не тестировать downgrade вообще, полагаясь, что откат никогда не понадобится, хотя именно он часто нужен при экстренном rollback релиза.