Как Unit test с разными настройками в Django?

Есть ли простой механизм для переопределения настроек Django для unit test? У меня есть менеджер на одной из моих моделей, которая возвращает определенное количество последних объектов. Количество возвращаемых объектов определяется параметром NUM_LATEST.

Это может привести к сбою моих тестов, если кто-то изменит настройку. Как я могу переопределить настройки на setUp() и впоследствии восстановить их на tearDown()? Если это невозможно, можно ли каким-то образом обезвредить метод или издеваться над настройками?

EDIT: Вот мой код менеджера:

class LatestManager(models.Manager):
    """
    Returns a specific number of the most recent public Articles as defined by 
    the NEWS_LATEST_MAX setting.
    """
    def get_query_set(self):
        num_latest = getattr(settings, 'NEWS_NUM_LATEST', 10)
        return super(LatestManager, self).get_query_set().filter(is_public=True)[:num_latest]

Менеджер использует settings.NEWS_LATEST_MAX для среза запроса. getattr() просто используется для предоставления значения по умолчанию, если параметр не существует.

Ответ 1

EDIT: этот ответ применяется, если вы хотите изменить настройки для малого числа конкретных тестов.

Начиная с Django 1.4, есть способы переопределить настройки во время тестов: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase будет иметь диспетчер контекстов self.settings, а также будет декоратор @override_settings, который может быть применен либо к тестовому методу, либо к подклассу TestCase.

Эти функции еще не существовали в Django 1.3.

Если вы хотите изменить настройки для all своих тестов, вы захотите создать отдельный файл настроек для теста, который может загружать и переопределять настройки из файла основных настроек. В других ответах есть несколько хороших подходов; Я видел успешные варианты на hspander и dmitrii.

Ответ 2

Вы можете делать все, что вам нравится в подклассе UnitTest, включая свойства экземпляра и чтения экземпляра:

from django.conf import settings

class MyTest(unittest.TestCase):
   def setUp(self):
       self.old_setting = settings.NUM_LATEST
       settings.NUM_LATEST = 5 # value tested against in the TestCase

   def tearDown(self):
       settings.NUM_LATEST = self.old_setting

Так как тестовые примеры django работают с однопоточными, мне любопытно, что еще может изменить значение NUM_LATEST? Если это "что-то другое" вызвано вашей тестовой процедурой, я не уверен, что любое количество исправлений обезьяны сохранит тест, не нарушая достоверности самих тестов.

Ответ 3

Обновление: нижеприведенное решение требуется только для Django 1.3.x и более ранних версий. Для > 1.4 см. ответ slinkp.

Если вы часто меняете настройки в своих тестах и ​​используете Python ≥2.5, это также удобно:

from contextlib import contextmanager

class SettingDoesNotExist:
    pass

@contextmanager
def patch_settings(**kwargs):
    from django.conf import settings
    old_settings = []
    for key, new_value in kwargs.items():
        old_value = getattr(settings, key, SettingDoesNotExist)
        old_settings.append((key, old_value))
        setattr(settings, key, new_value)
    yield
    for key, old_value in old_settings:
        if old_value is SettingDoesNotExist:
            delattr(settings, key)
        else:
            setattr(settings, key, old_value)

Затем вы можете сделать:

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()

Ответ 4

Вы можете передать опцию --settings при запуске тестов

python manage.py test --settings=mysite.settings_local

Ответ 5

Хотя переопределение настроек конфигурации во время выполнения может помочь, на мой взгляд, вы должны создать отдельный файл для тестирования. Это позволяет сэкономить массу настроек для тестирования, и это гарантирует, что вы никогда не будете делать что-то необратимое (например, очистить промежуточную базу данных).

Скажите, что ваш тестовый файл существует в 'my_project/test_settings.py', добавьте

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'

в файле manage.py. Это гарантирует, что при запуске python manage.py test вы используете только test_settings. Если вы используете какой-либо другой клиент для тестирования, такой как pytest, вы можете легко добавить его в pytest.ini

Ответ 6

@override_settings отлично, если у вас не так много различий между конфигурациями вашей продукции и тестирования.

В другом случае вам лучше иметь разные файлы настроек. В этом случае ваш проект будет выглядеть следующим образом:

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py

Таким образом, вам нужно иметь максимальную настройку в base.py, а затем в других файлах вам нужно будет импортировать все, что угодно, и переопределить некоторые параметры. Здесь будет выглядеть ваш файл test.py:

from .base import *

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'app_db_test'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

И тогда вам либо нужно указать опцию --settings, как в ответе @MicroPyramid, либо указать переменную среды DJANGO_SETTINGS_MODULE, а затем вы можете запустить свои тесты:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

Ответ 7

Нашел это, пытаясь исправить некоторые доктрины... Для полноты я хочу упомянуть, что если вы собираетесь изменять настройки при использовании доктрин, вы должны сделать это, прежде чем импортировать что-нибудь еще...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc

Ответ 8

Я использую pytest.

Мне удалось решить это следующим образом:

import django    
import app.setting
import modules.that.use.setting

# do some stuff with default setting
setting.VALUE = "some value"
django.setup()
import importlib
importlib.reload(app.settings)
importlib.reload(modules.that.use.setting)
# do some stuff with settings new value

Ответ 9

Вы можете изменить настройки в тесте следующим образом:

from django.test import TestCase, override_settings

test_settings = override_settings(
    DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage',
    PASSWORD_HASHERS=(
        'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    )
)


@test_settings
class SomeTestCase(TestCase):
    """Your test cases in this class"""

И если вам нужны те же настройки в другом файле, вы можете просто импортировать test_settings.

Ответ 10

Если у вас есть несколько тестовых файлов, размещенных в подкаталоге (пакет python), вы можете переопределить настройки для всех этих файлов в зависимости от наличия строки 'test' в sys.argv

app
  tests
    __init__.py
    test_forms.py
    test_models.py

__init__.py:

import sys
from project import settings

if 'test' in sys.argv:
    NEW_SETTINGS = {
        'setting_name': value,
        'another_setting_name': another_value
    }
    settings.__dict__.update(NEW_SETTINGS)

Не лучший подход. Используйте его, чтобы изменить брокера из сельдерея с Redis на Memory.