Перейти к содержанию

PostgreSQL: тестирование

Что тестовые инструменты hare делают на PostgreSQL сверх того, что Настройка тестов описывает для любой базы.

Повторное использование тестовых баз: reuse_databases

Каждый DROP DATABASE заставляет PostgreSQL запросить контрольную точку, и на сервере с включённым fsync при параллельном запуске тестов это стоит секунд — дольше, чем создать и заполнить базу. При reuse_databases=True адрес PostgreSQL, в имени базы которого есть место {}, берёт базу из пула процесса (ReusableTestDatabases), а не создаёт новую со случайным именем:

  • место {} заполняется идентификатором ячейки пула — 32 шестнадцатеричных символа, полученных из номера ячейки (и рабочего процесса pytest-xdist), поэтому test_{} даёт одни и те же имена при каждом запуске;
  • при входе база ячейки создаётся, если её ещё нет, сбрасывается, если осталась от прошлого запуска, и используется как есть, если этот процесс её уже сбросил;
  • при выходе вместо DROP DATABASE база сбрасывается, а ячейка возвращается в пул — и тогда, когда блок бросил исключение или не удался сам сброс (тогда база сбрасывается перед следующим использованием);
  • контекст, в который входят, когда все ячейки заняты (вложенные контексты, несколько подключений в одной конфигурации), получает новую ячейку, поэтому пул вырастает до наибольшего числа баз, используемых одновременно.

Сброс возвращает базу в состояние только что созданной, не удаляя её: завершает другие подключённые к ней сеансы (pg_terminate_backend), откатывает её подготовленные транзакции, удаляет все схемы, кроме системных (DROP SCHEMA ... CASCADE — это удаляет и установленные в них расширения: vector, postgis, citext, btree_gist и другие), заново создаёт public с владельцем, правами и комментарием, как у новой базы, и очищает настройки уровня базы (ALTER DATABASE ... RESET ALL и ALTER ROLE ... IN DATABASE ... RESET ALL для каждой роли, у которой они есть). Ни одна из этих команд не запрашивает контрольную точку. Объекты вне схем — большие объекты, триггеры событий, публикации — не удаляются.

За пулером в режиме транзакций сброс выполняется на самом сервере (direct_host) и завершает на базе и серверные сеансы пулера. PgBouncer узнаёт о завершённом сеансе, только когда выдаёт его, поэтому первый оператор теста упал бы на нём: задайте в PgBouncer тестов server_check_delay = 0 — тогда он проверяет каждое серверное подключение перед выдачей и заменяет то, которое завершил сброс.

reuse_databases=None (по умолчанию) читает переменную окружения HARE_TEST_REUSE_DATABASES: 1/true включает пул, 0/false или отсутствие переменной — нет; любое другое значение даёт ConfigurationError. Явный аргумент True/False важнее переменной. Адреса SQLite и адреса PostgreSQL без {} в пул никогда не попадают.

HARE_TEST_REUSE_DATABASES=1 pytest -n 8

Базы из пула не удаляются при завершении процесса — их забирает следующий запуск, потому что test_{} снова даёт те же имена. Тест, проверяющий сам жизненный цикл базы, — отказ CREATE DATABASE для существующего имени, DROP DATABASE при активном соединении — должен передавать reuse_databases=False.