Как провести модульное тестирование с разными настройками в Django?

Есть ли простой механизм переопределения настроек Django для модульного теста? У меня есть менеджер одной из моих моделей, который возвращает определенное количество последних объектов. Количество возвращаемых объектов определяется параметром 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() просто используется для предоставления значения по умолчанию, если настройка не существует.


person Soviut    schedule 27.05.2009    source источник
comment
@Анто - можешь объяснить почему или дать лучший ответ?   -  person user    schedule 13.01.2015
comment
Тем временем он изменился; прежний принятый был этот;)   -  person Anto    schedule 14.01.2015


Ответы (13)


РЕДАКТИРОВАТЬ: этот ответ применим, если вы хотите изменить настройки для небольшого количества конкретных тестов.

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

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

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

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

person slinkp    schedule 20.06.2011
comment
Я бы сказал, что это лучший способ сделать это сейчас в Django 1.4+. - person Michael Mior; 28.06.2013
comment
Как вы позже получите доступ к этому параметру из тестов? Лучшее, что я нашел, это что-то вроде self.settings().wrapped.MEDIA_ROOT, но это довольно ужасно. - person mlissner; 22.10.2014
comment
В новых версиях Django для этого есть специальный менеджер контекста: docs .djangoproject.com/en/1.8/topics/testing/tools/ - person Akhorus; 06.11.2015
comment
Мой любимый: @modify_settings(MIDDLEWARE_CLASSES=... (спасибо за этот ответ) - person guettli; 14.10.2016

Вы можете делать с подклассом 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? Если это «что-то еще» вызвано вашей процедурой тестирования, то я не уверен, что какое-либо количество исправлений обезьяны спасет тест, не лишая достоверности самих тестов.

person Jarret Hardie    schedule 27.05.2009
comment
Ваш пример сработал. Это открыло глаза с точки зрения объема модульного тестирования и того, как настройки в файле тестов распространяются вниз по стеку вызовов. - person Soviut; 27.05.2009
comment
Это не работает с settings.TEMPLATE_LOADERS... Так что, по крайней мере, это не общий способ, настройки или Django не перезагружаются или что-то еще с этим трюком. - person Ciantic; 17.07.2010
comment
это хороший пример для версии Django старше 1.4. Для ›= 1.4 ответьте stackoverflow.com/a/6415129/190127 более правильно - person Oduvan; 09.01.2013
comment
Используйте docs.djangoproject.com/en/dev/topics/ testing/tools/ Исправление с помощью setUp и tearDown, подобное этому, — отличный способ сделать действительно хрупкие тесты, которые являются более подробными, чем они должны быть. Если вам нужно исправить что-то подобное, используйте что-то вроде flexmock. - person fuzzy-waffle; 29.05.2014
comment
Поскольку тестовые примеры django выполняются в однопоточном режиме, чего больше нет в Django 1.9. - person Wtower; 03.12.2015

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

python manage.py test --settings=mysite.settings_local
person MicroPyramid    schedule 10.06.2016
comment
он перестал находить приложения, которые находятся в settings.dev, который является расширением settings.base - person holms; 26.02.2018
comment
Думаю, будет опасно, если кто-то один раз забудет добавить параметры настроек. - person ramwin; 12.06.2018

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

Скажите, что ваш тестовый файл существует в '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.

person hspandher    schedule 15.06.2015
comment
Я думаю, что это хорошее решение для меня. У меня слишком много тестов и кода, который использует кеш. Мне будет сложно переопределить настройки одну за другой. Я создам два файла конфигурации и определяю, какой из них использовать. Ответ MicroPyramid также доступен, но будет опасно, если я один раз забуду добавить параметры настройки. - person ramwin; 12.06.2018

Обновление: приведенное ниже решение требуется только для 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()
person akaihola    schedule 19.08.2010
comment
Это действительно крутое решение. По какой-то причине мои настройки не работали должным образом в модульных тестах. Очень элегантное решение, спасибо, что поделились. - person Tomas; 23.03.2012
comment
Я использую этот код, но у меня были проблемы с каскадными ошибками теста, потому что настройки не будут восстановлены, если рассматриваемый тест не пройден. Чтобы решить эту проблему, я добавил оператор try/finally вокруг оператора yield, при этом последняя часть функции содержалась в блоке finally, так что настройки всегда возвращаются. - person Dustin Rasener; 29.05.2012
comment
Я отредактирую ответ для потомков. Я надеюсь, что я делаю это правильно! :) - person Dustin Rasener; 29.05.2012

@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 
person Dmitrii Mikhailov    schedule 27.12.2016
comment
Привет . Дмитрий, спасибо за ваш ответ, у меня тот же случай с этим ответом, но я хотел бы получить больше указаний о том, как приложение будет знать, среда, в которой мы находимся (тестирование или производство), иметь посмотрите мою ветку, проверьте мой репозиторий github.com/andela/ah -backend-iroquois/tree/develop/authors, например, как я буду обрабатывать эту логику? - person Lutaaya Huzaifah Idris; 30.08.2018
comment
Поскольку я использую nosetests для запуска тестов, как это будет выполняться? В среде тестирования, а не в среде разработки - person Lutaaya Huzaifah Idris; 30.08.2018

Для пользователей pytest.

Самая большая проблема:

  • override_settings не работает с pytest.
  • Подкласс Django TestCase заставит его работать, но тогда вы не сможете использовать фикстуры pytest.

Решение состоит в том, чтобы использовать приспособление settings, задокументированное здесь.

Пример

def test_with_specific_settings(settings):
    settings.DEBUG = False
    settings.MIDDLEWARE = []
    ..

И если вам нужно обновить несколько полей

def override_settings(settings, kwargs):
    for k, v in kwargs.items():
        setattr(settings, k, v)


new_settings = dict(
    DEBUG=True,
    INSTALLED_APPS=[],
)


def test_with_specific_settings(settings):
    override_settings(settings, new_settings)
person Pithikos    schedule 07.11.2019

Вы можете переопределить настройку даже для одной тестовой функции.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

    @override_settings(SOME_SETTING="some_value")
    def test_some_function():
        

или вы можете переопределить настройку для каждой функции в классе.

@override_settings(SOME_SETTING="some_value")
class SomeTestCase(TestCase):

    def test_some_function():
        
person shivansh    schedule 01.07.2020

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

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc
person Jiaaro    schedule 14.12.2009

Я использую питест.

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

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
person Brontes    schedule 10.04.2018

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

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.

person giantas    schedule 07.09.2019

Я создал новый файл settings_test.py, который будет импортировать все из файла settings.py и изменять все, что отличается для целей тестирования. В моем случае я хотел использовать другую корзину облачного хранилища при тестировании. введите здесь описание изображения

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

manage.py:

def main():

    # use seperate settings.py for tests
    if 'test' in sys.argv:
        print('using settings_test.py')
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings_test')
    else:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)
person Aseem    schedule 11.07.2020

Если у вас есть несколько тестовых файлов, размещенных в подкаталоге (пакет Python), вы можете переопределить настройки для всех этих файлов в зависимости от наличия строки «тест» в 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)

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

person Ledorub    schedule 27.09.2019