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.

Soviut
quelle
@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 .

Slinkp
quelle
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.

Jarret Hardie
quelle
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

hspandher
quelle
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
MicroPyramid
quelle
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()
Akaihola
quelle
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 
Dmitrii Mikhailov
quelle
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
Jiaaro
quelle
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)
Pithikos
quelle
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
Brontes
quelle
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.

Riesen
quelle
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():
        
Shivansh
quelle
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.

Ledorub
quelle
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)
Aseem
quelle