Jak przeprowadzić testy jednostkowe z różnymi ustawieniami w Django?


117

Czy istnieje prosty mechanizm zastępowania ustawień Django w teście jednostkowym? Mam menedżera na jednym z moich modeli, który zwraca określoną liczbę najnowszych obiektów. Liczba zwracanych obiektów jest zdefiniowana przez ustawienie NUM_LATEST.

Może to spowodować niepowodzenie moich testów, gdyby ktoś zmienił ustawienie. Jak mogę zmienić ustawienia, setUp()a następnie przywrócić je tearDown()? Jeśli to nie jest możliwe, czy jest jakiś sposób, w jaki mogę małpować poprawkę metody lub kpić z ustawień?

EDYCJA: Oto mój kod menedżera:

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]

Menedżer używa settings.NEWS_LATEST_MAXdo dzielenia zestawu zapytań. getattr()Jest po prostu wykorzystane do zapewnienia domyślne ustawienie nie powinien istnieć.


@Anto - czy możesz wyjaśnić dlaczego lub udzielić lepszej odpowiedzi?
użytkownik

W międzyczasie to się zmieniło; ten pierwszy zaakceptowany to ten ;)
Anto

Odpowiedzi:


164

EDYCJA: Ta odpowiedź ma zastosowanie, jeśli chcesz zmienić ustawienia dla niewielkiej liczby określonych testów.

Od wersji Django 1.4 istnieją sposoby na nadpisanie ustawień podczas testów: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase będzie miał menedżera kontekstu self.settings, a także dekorator @override_settings, który można zastosować do metody testowej lub całej podklasy TestCase.

Te funkcje nie istniały jeszcze w Django 1.3.

Jeśli chcesz zmienić ustawienia dla wszystkich testów, zechcesz utworzyć osobny plik ustawień do testu, który będzie mógł ładować i zastępować ustawienia z głównego pliku ustawień. W innych odpowiedziach jest kilka dobrych podejść do tego zagadnienia; Widziałem udane odmiany zarówno podejścia hspandera, jak i dmitrii .


4
Powiedziałbym, że to najlepszy sposób na zrobienie tego teraz w Django 1.4+
Michael Mior

Jak później uzyskujesz dostęp do tego ustawienia z poziomu testów? Najlepsze, jakie znalazłem, to coś w rodzaju self.settings().wrapped.MEDIA_ROOT, ale to dość okropne.
mlissner

2
Nowsze wersje Django mają do tego specjalnego menedżera kontekstu: docs.djangoproject.com/en/1.8/topics/testing/tools/ ...
Akhorus

Mój ulubiony: @modify_settings(MIDDLEWARE_CLASSES=...(dziękuję za tę odpowiedź)
guettli

44

Możesz zrobić wszystko, co chcesz z UnitTestpodklasą, w tym ustawiać i odczytywać właściwości instancji:

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

Ponieważ przypadki testowe django działają jednowątkowo, jestem ciekaw, co jeszcze może modyfikować wartość NUM_LATEST? Jeśli to „coś innego” zostanie wywołane przez twoją procedurę testową, to nie jestem pewien, czy jakakolwiek ilość małpich poprawek zapisze test bez unieważnienia prawdziwości samych testów.


Twój przykład zadziałał. Otworzyło to oczy, jeśli chodzi o zakres testów jednostkowych i sposób propagacji ustawień w pliku testów w stosie wywołań.
Soviut

To nie działa z settings.TEMPLATE_LOADERS... Więc to nie jest przynajmniej ogólny sposób, ustawienia lub Django nie są przeładowywane ani nic z tą sztuczką.
Ciantic,

1
jest to dobry przykład dla wersji Django starszej niż 1.4. Dla> = 1.4 odpowiedź stackoverflow.com/a/6415129/190127 bardziej poprawna
Oduvan

Skorzystaj z docs.djangoproject.com/en/dev/topics/testing/tools/ ... Stosowanie poprawek za pomocą setUp i tearDown w ten sposób to świetny sposób na tworzenie naprawdę delikatnych testów, które są bardziej szczegółowe niż powinny. Jeśli chcesz załatać coś takiego, użyj czegoś takiego jak flexmock.
rozmyty gofr

„Ponieważ przypadki testowe django działają jednowątkowo”: co nie ma już miejsca w Django 1.9.
Wtower

22

Chociaż zastąpienie konfiguracji ustawień w czasie wykonywania może pomóc, moim zdaniem powinieneś utworzyć osobny plik do testowania. Oszczędza to dużo konfiguracji do testowania i zapewniłoby, że nigdy nie zrobisz czegoś nieodwracalnego (np. Wyczyszczenie pomostowej bazy danych).

Powiedz, że plik testowy istnieje w „my_project / test_settings.py”, dodaj

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

w twoim manage.py. Zapewni to, że po uruchomieniu python manage.py testbędziesz używać tylko ustawień test_settings. Jeśli używasz innego klienta testowego, takiego jak pytest, możesz równie łatwo dodać to do pytest.ini


2
Myślę, że to dla mnie dobre rozwiązanie. Mam zbyt wiele testów i kodu, który używa pamięci podręcznej. Trudno będzie mi zmienić ustawienia jeden po drugim. Utworzę dwa pliki konfiguracyjne i określę, którego użyć. Odpowiedź MicroPyramid jest również dostępna, ale będzie niebezpiecznie, gdybym zapomniał raz dodać parametry ustawień.
ramwin

22

Możesz przejść --settingsopcję podczas uruchamiania testów

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

przestał znajdować aplikacje, które znajdują się w settings.dev, które jest rozszerzeniem settings.base
holms

4
Myślę, że będzie to niebezpieczne, jeśli ktoś raz zapomni dodać parametry ustawień.
Ramwin

20

Aktualizacja : poniższe rozwiązanie jest potrzebne tylko w Django 1.3.x i wcześniejszych. Dla> 1.4 zobacz odpowiedź slinkpa .

Jeśli często zmieniasz ustawienia w swoich testach i używasz Pythona ≥2,5, jest to również przydatne:

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)

Następnie możesz:

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

To naprawdę fajne rozwiązanie. Z jakiegoś powodu moje ustawienia nie działały poprawnie w testach jednostkowych. Bardzo eleganckie rozwiązanie, dzięki za udostępnienie.
Tomas

Używam tego kodu, ale miałem problemy z kaskadowymi błędami testów, ponieważ ustawienia nie zostałyby przywrócone, gdyby dany test się nie powiódł. Aby rozwiązać ten problem, dodałem próbę / w końcu wokół yieldinstrukcji, z ostatnią częścią funkcji zawartą w finallybloku, aby ustawienia były zawsze przywracane.
Dustin Rasener

Zmienię odpowiedź dla potomności. Mam nadzieję, że robię to dobrze! :)
Dustin Rasener

11

@override_settings jest świetny, jeśli nie ma wielu różnic między konfiguracjami środowiska produkcyjnego i testowego.

W innym przypadku lepiej po prostu mieć inne pliki ustawień. W takim przypadku Twój projekt będzie wyglądał następująco:

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

Musisz więc mieć większość swoich ustawień w, base.pya następnie w innych plikach musisz zaimportować wszystko stamtąd i zastąpić niektóre opcje. Oto jak test.pybędzie wyglądał Twój plik:

from .base import *

DEBUG = False

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

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

LOGGING = {}

Następnie albo musisz podać --settingsopcję jak w odpowiedzi @MicroPyramid, lub określić DJANGO_SETTINGS_MODULEzmienną środowiskową, a następnie możesz uruchomić testy:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

Witaj . Dmitrii, dziękuję za odpowiedź. Mam ten sam przypadek z tą odpowiedzią, ale chciałbym uzyskać więcej wskazówek na temat tego, jak aplikacja będzie wiedzieć, środowisko, w którym się znajdujemy (testowanie lub produkcja) , spójrz na mój oddział, sprawdź moje repozytorium github.com/andela/ah-backend-iroquois/tree/develop/authors , na przykład jak sobie poradzę z tą logiką?
Lutaaya Huzaifah Idris

Ponieważ używam testów nosetesty do uruchamiania testów, teraz jak to będzie działać ?, w środowisku testowym, a nie w środowisku programistycznym
Lutaaya Huzaifah Idris

3

Znalazłem to podczas próby naprawienia niektórych dokumentów ... Dla kompletności chcę wspomnieć, że jeśli zamierzasz zmodyfikować ustawienia podczas korzystania z doctestów, powinieneś to zrobić przed zaimportowaniem czegokolwiek innego ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

3

O pytest użytkowników.

Największym problemem jest:

  • override_settings nie działa z pytest.
  • Podklasy Django TestCase sprawią, że to zadziała, ale nie możesz wtedy używać urządzeń pytest.

Rozwiązaniem jest użycie settingsudokumentowanego mocowania tutaj .

Przykład

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

I na wypadek, gdybyś musiał zaktualizować wiele pól

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

Możesz zmienić ustawienie nawet dla pojedynczej funkcji testowej.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

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

lub możesz nadpisać ustawienie dla każdej funkcji w klasie.

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

    def test_some_function():
        

1

Używam pytest.

Udało mi się to rozwiązać w następujący sposób:

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

Możesz zastąpić ustawienia w teście w następujący sposób:

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"""

A jeśli potrzebujesz tych samych ustawień w innym pliku, możesz po prostu zaimportować je bezpośrednio test_settings.


0

Jeśli masz wiele plików testowych umieszczonych w podkatalogu (pakiet Pythona), możesz nadpisać ustawienia dla wszystkich tych plików na podstawie warunku obecności ciągu „test” w 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)

Nie jest to najlepsze podejście. Użyto go do zmiany brokera Selera z Redis na Memory.


0

Utworzyłem nowy plik settings_test.py, który importowałby wszystko z pliku settings.py i modyfikował wszystko, co było inne do celów testowych. W moim przypadku podczas testowania chciałem użyć innego zasobnika do przechowywania w chmurze. wprowadź opis obrazu tutaj

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.