После смены БД в Django с SQLite на PostgreSQL не проходит часть тест кейсов
Прошу помощи. Есть работающий проект на Django, использовалась БД SQLite, с ней же были написаны TestCase, все проходили как нужно. Сменил БД на PostgreSQL и тесты начали падать с исключениями.
- Тесты проходят всегда по разному - в первый раз после настройки новой БД упали почти все, но при этом при запуске отдельных тестов они могли успешно проходить.
- В текущей ситуации при запуске всех тестов
python3 manage.py testпадает с исключениями только один тестовый модуль, но при этом запуск отдельного TestCase из этого модуля может пройти, но чаще падает с исключением. - При тестировании руками через браузер все кейсы проходят успешно, никаких ошибок не возникает.
- Код до и после смены БД не изменялся.
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'my_db',
'USER': 'USER',
'PASSWORD': 'PASSWORD',
'HOST': 'localhost',
'PORT': '5432',
'TEST': {
'TEMPLATE': 'test_db',
}
}
}
test_views.py # код одного из падающих тестов
class TestCoreViews(TestCase):
number_of_persons = None
@classmethod
def setUpTestData(cls):
# создает первого шаблонного Person
# остальные со случайными данными
person = get_test_person()
person.save()
number_of_persons = 1
for person in mim_do_persons(10, True): # собственный генератор экземпляров для БД, используется и в других тестах, которые проходят
person.save()
number_of_persons += 1
cls.number_of_persons = number_of_persons
test_user = User.objects.create_user(username='test', password='test')
test_user.save()
def login_test_user(self): # функция аутентификации для удобства, представления обязательно ее требуют
self.client.login(username='test', password='test')
def test_url_exist(self): # непосредственно тест
self.login_test_user()
for number in range(1, self.number_of_persons+1):
response = self.client.get(f'/core/person/{number}/') # вот здесь тест падает на первой же итерации
self.assertEqual(response.status_code, 200)
Само исключение:
response = self.client.get(f'/core/person/{number}/')
...
core.models.Person.DoesNotExist: Person matching query does not exist.
У меня очень большое подозрение, что django тут не причем, а что то не так с POstgreSQL, но не удается найти какой либо информации что вообще может быть не так. Единственное что более менее подходило под мою ситуацию - выполнение авторизации поверх уже выполненной, но SetUpTestData сбрасывает БД перед каждым тестом, а перестраховка в виде self.client.logout() не дает никакого результата. И повторюсь - по какой то странной логике тесты иногда проходят.
Сам PostgreSQL настраивал: для юзера БД и создана отдельная тестовая БД, и даны привелегии на создание новых БД (alter role username createdb).
Ответы (2 шт):
Нашел проблему.
Тестирование с обращением к записям по url формата f'core/person/{id}/' строилось из предположения что на каждый тест создается новая (чистая/пустая) база данных, и обращение к первому созданному объекту возможно по его id==1 ('core/person/1/'). И в sqlite это действительно работало.
При запуске ВСЕХ тестов с PSQL база действительно отчищается, но не "в ноль" - со второго TestCase и далее БД вычищает предыдущие записи, но при этом делает недоступными для записи ранее использованные id. Т.е. на последних тестах первый созданный и записанный экзепляр может получить id, к примеру, 14. И попытка обратиться к к нему по id=1 падает с исключением DoesNotExist. Решается сохранением id первого созданного элемента атрибутом класса, в def SetUp(self), и применением его вместо id=1 (а как Person.objects.get(id=self.first_person_id) ). По этой же причине отдельно запущеный TestCase принимал использование id=1.
Вопрос решен, но буду благодарен за ссылку на объяснение данного феномена.
При создании базы с нуля - все первичные ключи будут нумероваться заново. При очистке таблиц (удалении существующих записей) не происходит инициализация первичных ключей. Все вновь созданные записи будут иметь id с продолжающейся нумерацией, как вы и описали. Для полного сброса базы имеет смысл удалить ее DROP DATABASE dev WITH (FORCE); Затем создать новую базу с этим же именем CREATE DATABASE dev WITH TEMPLATE template0 OWNER postgres; После запустить миграции Django - ./manage.py migrate К тому же эти операции выполнятся гораздо быстрее, чем удаление всех записей во всех таблицах.