2025, Oct 18 19:16

Как избежать обращения тестов Django/pytest к рабочей базе

Разбираем, почему Django и pytest/pytest-django запускают тесты на рабочей БД, как проверить подключение и вернуть создание тестовой test_БД по умолчанию.

Наборы тестов Django должны выполняться в изолированной базе данных, а не в вашей рабочей. Тем не менее, при связке pytest, pytest-django и пользовательского запуска тестов из IDE удивительно легко случайно обратиться к основной базе. Ниже — краткое объяснение, что идет не так, как проверить, с какой базой ваши тесты действительно работают, и какая минимальная настройка возвращает безопасное поведение Django по умолчанию.

Симптом

Тесты запускаются через тестовый раннер VS Code в проекте на Django 3.2.25 с pytest 8.2.2. Ожидалось, что для выполнения тестов будет динамически создана одноразовая база. Вместо этого код показывает, что соединение указывает на обычное имя базы данных.

Демонстрация проблемы

В конфигурации базы данных явно задан раздел TEST, а тест печатает активное имя базы данных:

DATABASES = {
    'default': {
        "NAME": "localDatabase",
        "ENGINE": "django.contrib.gis.db.backends.postgis",
        "USER": "test",
        "PASSWORD": "test",
        "HOST": "127.0.0.1",
        "PORT": "5432",
        "TEST": {
            "NAME": "test_local"
        },
    },
}
from rest_framework.test import APITestCase
from rest_framework import status
from django.db import connection

class InvoiceApiSpec(APITestCase):
    def test_can_create_invoice(self):
        print(connection.settings_dict["NAME"])  # Ожидалось имя тестовой БД
        resp = self.client.post(self.endpoint, self.sample_payload, format="json")
        self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

В вывод попадает localDatabase, а не ожидаемое имя тестовой базы.

Что на самом деле происходит

По умолчанию Django создает отдельную тестовую базу, добавляя префикс test_ к имени, указанному в настройках. Это встроенное поведение при запуске тестов. Но устаревшая фикстура pytest в conftest.py переопределила стандартный механизм создания тестовой базы. В итоге раннер не подготовил временную БД и продолжил использовать исходные настройки без изменений.

Чтобы проверить, какая база реально используется во время теста, получите имя соединения:

from django.db import connection
connection.settings_dict["NAME"]

Это выводит фактическую базу, к которой обращается тест во время выполнения, а не просто значение из settings.py.

Как исправить

Верните поведение Django по умолчанию: удалите переопределение, мешающее созданию тестовой базы, и избегайте принудительного указания имени через DATABASES["default"]["TEST"]. После удаления такого кода и ключа TEST Django автоматически создаст базу с именем test_<original_name> на время прогона тестов.

Рабочая конфигурация выглядит так:

DATABASES = {
    'default': {
        "NAME": "localDatabase",
        "ENGINE": "django.contrib.gis.db.backends.postgis",
        "USER": "test",
        "PASSWORD": "test",
        "HOST": "127.0.0.1",
        "PORT": "5432",
    },
}

С такой настройкой при запуске тестов используется временная база test_localDatabase. Нет нужды задавать TEST["NAME"] или вручную переписывать настройки в фикстуре. Попытки вроде settings.DATABASES["default"]["NAME"] = "test_local" обходят автоматическое создание и приводят к ошибкам подключения, потому что база не создается автоматически.

Почему это важно

Изоляция тестов предотвращает случайное повреждение данных и делает результаты воспроизводимыми. Доверив Django создание и управление временной тестовой базой, вы получаете чистое состояние между прогонами и избегаете скрытого взаимного влияния тестов. Проверка активного имени базы внутри теста — простой способ убедиться, что вы не попадаете в рабочую базу.

Итоги

Оставьте создание тестовой базы на Django по умолчанию. Уберите пользовательские переопределения в conftest.py, меняющие процесс подготовки баз, и не хардкодьте TEST["NAME"], если не требуется особое имя. Если сомневаетесь, какая база используется, выведите внутри теста connection.settings_dict["NAME"] — это текущее соединение, которое во время прогона должно указывать на автоматически созданную базу test_<name>.

Статья основана на вопросе с StackOverflow от Geo и ответе от Geo.