Wie kann ich mit verschiedenen Einstellungen in Django Unit-Tests durchführen?


116

Gibt es einen einfachen Mechanismus zum Überschreiben von Django-Einstellungen für einen Komponententest? Ich habe einen Manager für eines meiner Modelle, der eine bestimmte Anzahl der neuesten Objekte zurückgibt. Die Anzahl der zurückgegebenen Objekte wird durch eine NUM_LATEST-Einstellung definiert.

Dies kann dazu führen, dass meine Tests fehlschlagen, wenn jemand die Einstellung ändert. Wie kann ich die Einstellungen überschreiben setUp()und anschließend wiederherstellen tearDown()? Wenn das nicht möglich ist, gibt es eine Möglichkeit, die Methode zu patchen oder die Einstellungen zu verspotten?

EDIT: Hier ist mein Manager-Code:

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]

Der Manager settings.NEWS_LATEST_MAXschneidet das Abfrageset. Das getattr()wird einfach verwendet, um eine Standardeinstellung anzugeben, falls die Einstellung nicht vorhanden ist.


@Anto - kannst du erklären warum oder eine bessere Antwort geben?
Benutzer

Es hat sich inzwischen geändert; der erstere akzeptierte war dieser ;)
Anto

Antworten:


163

BEARBEITEN: Diese Antwort gilt, wenn Sie die Einstellungen für eine kleine Anzahl spezifischer Tests ändern möchten .

Seit Django 1.4 gibt es Möglichkeiten, Einstellungen während Tests zu überschreiben: https://docs.djangoproject.com/de/dev/topics/testing/tools/#overriding-settings

TestCase verfügt über einen Kontextmanager für self.settings und einen @ override_settings-Dekorator, der entweder auf eine Testmethode oder eine gesamte TestCase-Unterklasse angewendet werden kann.

Diese Funktionen gab es in Django 1.3 noch nicht.

Wenn Sie die Einstellungen für alle Ihre Tests ändern möchten , müssen Sie eine separate Einstellungsdatei für den Test erstellen, in der Einstellungen aus Ihrer Haupteinstellungsdatei geladen und überschrieben werden können. In den anderen Antworten gibt es mehrere gute Ansätze dafür; Ich habe erfolgreiche Variationen sowohl der Ansätze von hspander als auch von dmitrii gesehen .


4
Ich würde sagen, dies ist der beste Weg, dies jetzt in Django 1.4+ zu tun
Michael Mior

Wie greifen Sie später innerhalb der Tests auf diese Einstellung zu? Das Beste, was ich gefunden habe, ist so etwas wie self.settings().wrapped.MEDIA_ROOT, aber das ist ziemlich schrecklich.
mlissner

2
Neuere Versionen von Django haben dafür einen speziellen Kontextmanager: docs.djangoproject.com/de/1.8/topics/testing/tools/…
Akhorus

Mein Favorit: @modify_settings(MIDDLEWARE_CLASSES=...(Danke für diese Antwort)
Guettli

44

Sie können mit der UnitTestUnterklasse alles tun, was Sie möchten, einschließlich des Festlegens und Lesens von Instanzeigenschaften:

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

Da die Django-Testfälle jedoch mit einem Thread ausgeführt werden, bin ich gespannt, was den NUM_LATEST-Wert sonst noch ändern könnte. Wenn dieses "etwas anderes" von Ihrer Testroutine ausgelöst wird, bin ich mir nicht sicher, ob durch das Patchen von Affen der Test gespeichert wird, ohne die Richtigkeit der Tests selbst zu beeinträchtigen.


Ihr Beispiel hat funktioniert. Dies hat uns die Augen geöffnet, was den Umfang der Komponententests und die Weitergabe der Einstellungen in der Testdatei über den Aufrufstapel betrifft.
Soviut

Dies funktioniert nicht mit settings.TEMPLATE_LOADERS... Das ist also zumindest nicht allgemein, die Einstellungen oder Django werden nicht neu geladen oder irgendetwas mit diesem Trick.
Ciantic

1
Dies ist ein gutes Beispiel für die Version Django, die älter als 1.4 ist. Für> = 1.4 Antwort stackoverflow.com/a/6415129/190127 korrekter
Oduvan

Verwenden Sie docs.djangoproject.com/de/dev/topics/testing/tools/…. Das Patchen mit setUp und tearDown ist eine großartige Möglichkeit, um wirklich fragile Tests durchzuführen, die ausführlicher sind, als sie sein müssen. Wenn Sie so etwas patchen müssen, verwenden Sie etwas wie Flexmock.
Fuzzy-Waffel

"Da die Django-Testfälle Single-Threaded ausführen": Dies ist in Django 1.9 nicht mehr der Fall.
Wtower

22

Obwohl das Überschreiben der Konfiguration der Einstellungen zur Laufzeit hilfreich sein kann, sollten Sie meiner Meinung nach eine separate Datei zum Testen erstellen. Dies spart viel Konfiguration für Tests und würde sicherstellen, dass Sie niemals etwas Irreversibles tun (wie das Bereinigen der Staging-Datenbank).

Angenommen, Ihre Testdatei befindet sich in 'my_project / test_settings.py', fügen Sie hinzu

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

in Ihrer manage.py. Dadurch wird sichergestellt, dass Sie beim python manage.py testAusführen nur test_settings verwenden. Wenn Sie einen anderen Testclient wie pytest verwenden, können Sie diesen genauso einfach zu pytest.ini hinzufügen


2
Ich denke, das ist eine gute Lösung für mich. Ich habe viel zu viele Tests und Code, der Cache verwendet. Es wird für mich schwierig sein, die Einstellungen einzeln zu überschreiben. Ich werde zwei Konfigurationsdateien erstellen und bestimmen, welche verwendet werden soll. Die Antwort der MicroPyramid ist ebenfalls verfügbar, aber es ist gefährlich, wenn ich vergessen habe, die Einstellungsparameter einmal hinzuzufügen.
Ramwin

22

Sie können die --settingsOption beim Ausführen von Tests übergeben

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

Es wurde angehalten, um Apps zu finden, die sich in settings.dev befinden.
Dies

4
Ich denke, es wird gefährlich sein, wenn jemand vergisst, die Einstellungsparameter einmal hinzuzufügen.
Ramwin

20

Update : Die folgende Lösung wird nur für Django 1.3.x und früher benötigt. Für> 1.4 siehe die Antwort von slinkp .

Wenn Sie in Ihren Tests häufig Einstellungen ändern und Python ≥2,5 verwenden, ist dies auch praktisch:

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)

Dann können Sie tun:

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

Das ist wirklich coole Lösung. Aus irgendeinem Grund funktionierten meine Einstellungen in den Komponententests nicht richtig. Sehr elegante Lösung, danke fürs Teilen.
Tomas

Ich verwende diesen Code, hatte jedoch Probleme mit kaskadierenden Testfehlern, da die Einstellungen nicht zurückgesetzt werden, wenn der betreffende Test fehlschlägt. Um dies zu beheben, habe ich einen try / finally-Befehl um die yieldAnweisung hinzugefügt , wobei der letzte Teil der Funktion im finallyBlock enthalten ist, sodass die Einstellungen immer zurückgesetzt werden.
Dustin Rasener

Ich werde die Antwort für die Nachwelt bearbeiten. Ich hoffe ich mache das richtig! :)
Dustin Rasener

11

@override_settings ist großartig, wenn Sie nicht viele Unterschiede zwischen Ihren Produktions- und Testumgebungskonfigurationen haben.

In anderen Fällen sollten Sie nur andere Einstellungsdateien haben. In diesem Fall sieht Ihr Projekt folgendermaßen aus:

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

Sie müssen also die meisten Einstellungen in base.pyund dann in anderen Dateien haben, um alles von dort zu importieren und einige Optionen zu überschreiben. So test.pysieht Ihre Datei aus:

from .base import *

DEBUG = False

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

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

LOGGING = {}

Und dann müssen Sie entweder die --settingsOption wie in der Antwort von @MicroPyramid angeben oder die DJANGO_SETTINGS_MODULEUmgebungsvariable angeben , und dann können Sie Ihre Tests ausführen:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

Hallo . Dmitrii, danke für deine Antwort. Ich habe den gleichen Fall mit dieser Antwort, aber ich würde gerne mehr Anleitungen dazu erhalten, wie die App weiß, in welcher Umgebung wir uns befinden (Test oder Produktion) , einen Blick auf meine Branche werfen und nachsehen Mein Repo github.com/andela/ah-backend-iroquois/tree/develop/authors , wie werde ich mit dieser Logik umgehen?
Lutaaya Huzaifah Idris

Da ich Nosetests verwende , um Tests auszuführen, wie wird dies nun ausgeführt? In der Testumgebung, nicht in der Entwicklungsumgebung
Lutaaya Huzaifah Idris

3

Dies wurde beim Versuch gefunden, einige Doctests zu korrigieren ... Der Vollständigkeit halber möchte ich erwähnen, dass Sie dies tun sollten, bevor Sie etwas anderes importieren, wenn Sie die Einstellungen bei der Verwendung von Doctests ändern möchten ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

3

Für Pytest- Benutzer.

Das größte Problem ist:

  • override_settings funktioniert nicht mit pytest.
  • Wenn Sie Djangos TestCaseunterordnen, funktioniert es, aber dann können Sie keine Pytest-Geräte verwenden.

Die Lösung besteht darin, das hiersettings dokumentierte Gerät zu verwenden .

Beispiel

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

Und falls Sie mehrere Felder aktualisieren müssen

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)

1

Ich benutze Pytest.

Ich habe es folgendermaßen geschafft:

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

Sie können die Einstellungen im Test folgendermaßen überschreiben:

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

Und wenn Sie dieselben Einstellungen in einer anderen Datei benötigen, können Sie diese direkt importieren test_settings.


1

Sie können die Einstellung auch für eine einzelne Testfunktion überschreiben.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

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

oder Sie können die Einstellung für jede Funktion in der Klasse überschreiben.

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

    def test_some_function():
        

0

Wenn Sie mehrere Testdateien in einem Unterverzeichnis (Python-Paket) abgelegt haben, können Sie die Einstellungen für alle diese Dateien basierend auf der Bedingung des Vorhandenseins einer Testzeichenfolge in sys.argv überschreiben

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)

Nicht der beste Ansatz. Verwendet es, um den Sellerie-Broker von Redis in Memory zu ändern.


0

Ich habe eine neue Datei settings_test.py erstellt, die alles aus der Datei settings.py importiert und zu Testzwecken alle Änderungen ändert. In meinem Fall wollte ich beim Testen einen anderen Cloud-Speicher-Bucket verwenden. Geben Sie hier die Bildbeschreibung ein

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.