Изоляция кэша 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:

Обратите внимание, что каждый процесс будет иметь свой собственный частный экземпляр кэша, что означает, что межпроцессное кэширование невозможно.

Вернуться на верх