Як здійснити тестування з різними налаштуваннями в Django?


116

Чи існує якийсь простий механізм переосмислення параметрів 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()Просто використовується , щоб забезпечити значення за замовчуванням якщо параметр не існує.


@Anto - чи можете ви пояснити, чому або надати кращу відповідь?
користувач

Це тим часом змінилося; перший прийнятий був саме цей ;)
Анто

Відповіді:


163

EDIT: Ця відповідь застосовується, якщо ви хочете змінити налаштування для невеликої кількості конкретних тестів.

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

TestCase матиме контекстний менеджер самовстановок, а також буде декоратор @override_settings, який можна застосувати або до методу тестування, або до всього підкласу TestCase.

Ці функції ще не існували в Django 1.3.

Якщо ви хочете змінити налаштування для всіх своїх тестів, вам потрібно створити окремий файл налаштувань для тесту, який може завантажувати та змінювати параметри з вашого основного файлу налаштувань. До інших відповідей до цього є кілька хороших підходів; Я бачив успішні зміни як у підходах hspander, так і dmitrii .


4
Я б сказав, що це найкращий спосіб зробити це зараз у Django 1.4+
Майкл Міор

Як згодом ви отримуєте доступ до цього параметра з тестів? Найкраще, що я знайшов - це щось на кшталт self.settings().wrapped.MEDIA_ROOT, але це дуже жахливо.
mlissner

2
Новіші версії Django мають для цього специфічний менеджер контексту: docs.djangoproject.com/en/1.8/topics/testing/tools/…
Akhorus

Моя улюблена: @modify_settings(MIDDLEWARE_CLASSES=...(дякую за цю відповідь)
guettli

44

Ви можете робити все, що завгодно, до 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? Якщо це "щось інше" викликане вашою рутинною процедурою тестування, я не впевнений, що будь-яка кількість виправлення мавп врятує тест, не змінюючи правдивість самих тестів.


Ваш приклад спрацював. Це було відкриття очей щодо обсягу тестування одиниць та того, як налаштування у тестовому файлі поширюються вниз через стек викликів.
Радянський

Це не спрацьовує settings.TEMPLATE_LOADERS... Тож це, принаймні, не загальний спосіб, налаштування або Django не перезавантажуються або нічого з цим фокусом.
Ciantic

1
це хороший приклад для версії Django, старшої за 1.4. Для> = 1,4 відповіді stackoverflow.com/a/6415129/190127 точніше
Oduvan

Використовуйте docs.djangoproject.com/en/dev/topics/testing/tools/… Патчінг із налаштуваннями setUp та tearDown, як це, - це чудовий спосіб зробити дійсно крихкі тести, які є більш багатослівними, ніж потрібно. Якщо вам потрібно виправити щось подібне, використовуйте щось на зразок flexmock.
нечітка вафля

"Оскільки тестові приклади джанго виконуються однопотоково": у Django 1.9 це вже не так.
Wtower

22

Хоча конфігурація параметрів під час виконання може допомогти, на мою думку, ви повинні створити окремий файл для тестування. Це економить велику кількість конфігурації для тестування, і це гарантує, що ви ніколи не будете робити щось незворотне (наприклад, очищення бази даних етапів).

Скажімо, ваш тестовий файл існує у "my_project / test_settings.py", додайте

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

у вашому Manag.py. Це забезпечить, що під час запуску python manage.py testви використовуєте лише тестові набори. Якщо ви використовуєте інший тестовий клієнт, наприклад pytest, ви можете так само легко додати його до pytest.ini


2
Я думаю, це хороше рішення для мене. У мене дуже багато тестів і код, який використовує кеш. Мені буде важко переоцінити налаштування по черзі. Я створю два конфігураційні файли та визначу, який з них використовувати. Відповідь MicroPyramid також доступна, але це буде небезпечно, якщо я забув один раз додати параметри налаштувань.
ramwin

22

Ви можете пройти --settingsваріант під час запуску тестів

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

він зупинився, щоб знайти програми, які знаходяться в settings.dev, який є розширенням settings.base
holms

4
Я думаю, це буде небезпечно, якщо хтось забуде додати параметри налаштувань один раз.
ramwin

20

Оновлення : рішення нижче потрібне лише для 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()

Це дійсно круте рішення. Чомусь мої налаштування не працювали належним чином в тестових одиницях. Дуже елегантне рішення, дякую за обмін.
Томаш

Я використовую цей код, але у мене виникли проблеми з каскадними помилками тесту, тому що налаштування не повернуться, якщо тест не вдався. Щоб вирішити це, я додав спробу / нарешті навколо yieldзаяви, із заключною частиною функції, що міститься в finallyблоці, так що налаштування завжди повертаються.
Дастін Резенер

Я відредагую відповідь для нащадків. Я сподіваюся, що я роблю це правильно! :)
Дастін Резенер

11

@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 

Привіт . Дмитро, дякую за вашу відповідь, я маю той самий випадок із цією відповіддю, але я хотів би отримати більше вказівок щодо того, як буде знати додаток, середовище, в якому ми перебуваємо (тестування чи виробництво) , погляньте на мою гілку, перевірте мій репо github.com/andela/ah-backend-iroquois/tree/develop/authors , як, як я буду обробляти цю логіку?
Lutaaya Huzaifah Idris

Оскільки я використовую нос- тести для запуску тестів, тепер, як це буде працювати ?, в тестовому середовищі, а не в середовищі розробки
Lutaaya Huzaifah Idris,

3

Виявив це, намагаючись виправити деякі doctests ... Для повноти я хочу зазначити, що якщо ви збираєтесь змінювати налаштування під час використання doctests, ви повинні зробити це перед імпортом нічого іншого ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

3

Для пітестів користувачів .

Найбільше питання:

  • 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)

3

Ви можете змінити налаштування навіть для однієї тестової функції.

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():
        

1

Я використовую 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

1

Ви можете змінити налаштування в тесті таким чином:

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.


0

Якщо у підкаталозі розміщено кілька тестових файлів (пакунок 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.


0

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

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

Manag.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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.