Изоляция кэша Django при параллельном выполнении тестов
Когда я запускаю тесты параллельно, я получаю случайные сбои, потому что один тест вмешивается в кэш другого теста.
Я могу обойти проблему с помощью
@override_settings(
CACHES={
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "[random_string]",
}
},
)
На самом деле, чтобы сделать это меньше, я создал декоратор @isolate_cache, который является оберткой вокруг override_settings.
Но все равно мне нужно идти и оформлять большое количество тестовых примеров. Это чревато ошибками, потому что, как я уже сказал, сбои случайны. Я могу запустить набор тестов 100 раз без ошибок и думать, что все в порядке, но я все равно мог забыть украсить тестовый пример, и в какой-то момент произойдет случайный сбой.
Я также думал о том, чтобы создать свой собственный подкласс TestCase и использовать только его для всех своих тестовых примеров. Это создает аналогичную проблему: в какой-то момент кто-то по привычке унаследует от django.test.TestCase, и это может долгое время не давать сбоев. Кроме того, некоторые из моих тестов наследуются от rest_framework.test.APITestCase (или других классов), так что нет ни одного подкласса тестового случая.
Есть ли способ сказать Django запускать каждый тестовый пример в изолированной секции кэша раз и навсегда?
Самым простым решением будет отдельный файл настроек для тестов, который можно загрузить в manage.py. Это также может импортировать все ваши настройки по умолчанию.
manage.py
settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings)
test_settings.py
from .settings import * # import default settings
# setting overrides here
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "[random_string]",
}
}
Если вам нужно сделать больше переопределений настроек, особенно для нескольких сред, я бы рекомендовал использовать что-то вроде django-configurations.
Вам не нужна "изолированная секция кэша", только для очистки кэша между тестами.
Вот несколько способов.
1. Подкласс TestCase
В вопросе упоминается, что это нежелательно, но я все равно должен упомянуть этот правильный способ.
from django.core.cache import cache
from django.test import TestCase
class CacheClearTestCase(TestCase):
def tearDown(self):
# super().tearDown()
cache.clear()
2. Патч TestCase.tearDown
Предполагая, что подклассы, которые переопределяют tearDown, вызывают super().tearDown(), вы можете сделать следующее.
Добавьте это в файл manage.py перед execute_from_command_line(sys.argv):
if sys.argv[1] == 'test':
from django.test import TestCase
from django.core.cache import cache
TestCase.tearDown = cache.clear
3. Подкласс TestSuite
Вы можете очистить кэш после каждого теста, создав подкласс TestSuite для переопределения _removeTestAtIndex и установив DiscoverRunner.test_suite на этот подкласс.
Добавьте это в файл manage.py перед execute_from_command_line(sys.argv):
if sys.argv[1] == 'test':
from unittest import TestSuite
from django.core.cache import cache
from django.test.runner import DiscoverRunner
class CacheClearTestSuite(TestSuite):
def _removeTestAtIndex(self, index):
super()._removeTestAtIndex(index)
cache.clear()
DiscoverRunner.test_suite = CacheClearTestSuite
Почему вам не нужен изолированный участок кэша
Для ясности, это не проблема, вызванная параллельным запуском тестов.
From https://docs.djangoproject.com/en/4.0/ref/django-admin/#cmdoption-test-parallel:
--parallel [N]Запускает тесты в отдельных параллельных процессах.
From https://docs.djangoproject.com/en/4.0/topics/cache/#local-memory-caching-1:
Обратите внимание, что каждый процесс будет иметь свой собственный частный экземпляр кэша, что означает, что межпроцессное кэширование невозможно.