Come eseguire un test unitario con impostazioni diverse in Django?


116

Esiste un meccanismo semplice per sovrascrivere le impostazioni Django per uno unit test? Ho un manager su uno dei miei modelli che restituisce un numero specifico di oggetti più recenti. Il numero di oggetti restituiti è definito da un'impostazione NUM_LATEST.

Questo ha il potenziale per far fallire i miei test se qualcuno dovesse cambiare l'impostazione. Come posso sovrascrivere le impostazioni setUp()e successivamente ripristinarle tearDown()? Se ciò non è possibile, c'è un modo per correggere il metodo o deridere le impostazioni?

EDIT: ecco il mio codice manager:

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]

Il gestore utilizza settings.NEWS_LATEST_MAXper suddividere il set di query. Il getattr()è semplicemente utilizzato per fornire un default deve l'impostazione non esiste.


@Anto - puoi spiegare perché o fornire una risposta migliore?
utente

Nel frattempo è cambiato; il primo accettato era questo ;)
Anto

Risposte:


163

EDIT: questa risposta si applica se si desidera modificare le impostazioni per un numero limitato di test specifici .

A partire da Django 1.4, ci sono modi per sovrascrivere le impostazioni durante i test: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase avrà un gestore di contesto self.settings e ci sarà anche un decoratore @override_settings che può essere applicato a un metodo di test oa un'intera sottoclasse TestCase.

Queste funzionalità non esistevano ancora in Django 1.3.

Se desideri modificare le impostazioni per tutti i tuoi test, ti consigliamo di creare un file di impostazioni separato per il test, che può caricare e sovrascrivere le impostazioni dal file delle impostazioni principali. Ci sono molti buoni approcci a questo nelle altre risposte; Ho visto le variazioni di successo su entrambi di hspander e di Dmitrii approcci.


4
Direi che questo è il modo migliore per farlo ora in Django 1.4+
Michael Mior

Come si accede successivamente a tale impostazione dall'interno dei test? Il meglio che ho trovato è qualcosa del genere self.settings().wrapped.MEDIA_ROOT, ma è piuttosto terribile.
mlissner

2
Le versioni più recenti di Django hanno un gestore di contesto specifico per questo: docs.djangoproject.com/en/1.8/topics/testing/tools/…
Akhorus

Il mio preferito: @modify_settings(MIDDLEWARE_CLASSES=...(grazie per questa risposta)
guettli

44

Puoi fare tutto ciò che desideri per la UnitTestsottoclasse, inclusa l'impostazione e la lettura delle proprietà dell'istanza:

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

Poiché i casi di test django vengono eseguiti a thread singolo, tuttavia, sono curioso di sapere cos'altro potrebbe modificare il valore NUM_LATEST? Se quel "qualcos'altro" viene attivato dalla routine di test, allora non sono sicuro che nessuna quantità di patch scimmia salverà il test senza invalidare la veridicità dei test stessi.


Il tuo esempio ha funzionato. Ciò ha aperto gli occhi in termini di ambito di unit test e di come le impostazioni nel file di test si propagano verso il basso attraverso lo stack di chiamate.
Soviut

Questo non funziona con settings.TEMPLATE_LOADERS... Quindi questo non è almeno un modo generale, le impostazioni o Django non vengono ricaricati o altro con questo trucco.
Ciantic

1
questo è un buon esempio per la versione Django precedente alla 1.4. Per> = 1.4 risposta stackoverflow.com/a/6415129/190127~~V~~singular~~3rd più corretto
Oduvan

Usa docs.djangoproject.com/en/dev/topics/testing/tools/… Applicare patch con setUp e tearDown in questo modo è un ottimo modo per creare test davvero fragili che sono più dettagliati di quanto dovrebbero essere. Se hai bisogno di patchare qualcosa di simile usa qualcosa come flexmock.
fuzzy-waffle

"Dato che i test case di django girano a thread singolo": cosa che non è più il caso di Django 1.9.
Wtower

22

Sebbene l'override della configurazione delle impostazioni in runtime potrebbe aiutare, a mio parere dovresti creare un file separato per il test. Ciò consente di risparmiare molta configurazione per i test e questo garantirebbe che non si finisca mai per fare qualcosa di irreversibile (come la pulizia del database di staging).

Supponiamo che il tuo file di test esista in "my_project / test_settings.py", aggiungi

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

nel tuo manage.py. Questo assicurerà che quando esegui python manage.py testutilizzi solo test_settings. Se stai usando un altro client di test come pytest, potresti aggiungerlo facilmente a pytest.ini


2
Penso che questa sia una buona soluzione per me. Ho troppi test e codice che utilizza la cache. Sarà difficile per me sovrascrivere le impostazioni una per una. Creerò due file di configurazione e determinerò quale usare. È disponibile anche la risposta di MicroPyramid, ma sarebbe pericoloso se dimenticassi di aggiungere una volta i parametri delle impostazioni.
ramwin

22

È possibile passare l' --settingsopzione durante l'esecuzione dei test

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

si è fermato per trovare app che si trovano in settings.dev che è l'estensione di settings.base
holms

4
Penso che sarà pericoloso se qualcuno dimentica di aggiungere i parametri delle impostazioni una volta.
ramwin

20

Aggiornamento : la soluzione seguente è necessaria solo su Django 1.3.xe versioni precedenti. Per> 1.4 vedere la risposta di slinkp .

Se cambi le impostazioni frequentemente nei tuoi test e usi Python ≥2.5, anche questo è utile:

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)

Quindi puoi fare:

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

Questa è una soluzione davvero interessante. Per qualche motivo le mie impostazioni non funzionavano correttamente negli unit test. Soluzione molto elegante, grazie per la condivisione.
Tomas

Sto usando questo codice, ma ho avuto problemi con gli errori dei test a cascata, perché le impostazioni non sarebbero state ripristinate se il test in questione non fosse riuscito. Per risolvere questo problema, ho aggiunto una prova / finalmente intorno yieldall'istruzione, con la parte finale della funzione contenuta nel finallyblocco, in modo che le impostazioni vengano sempre ripristinate.
Dustin Rasener

Modificherò la risposta per i posteri. Spero di farlo bene! :)
Dustin Rasener

11

@override_settings è ottimo se non si hanno molte differenze tra le configurazioni dell'ambiente di produzione e di test.

In altri casi è meglio che tu abbia file di impostazioni differenti. In questo caso il tuo progetto sarà simile a questo:

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

Quindi devi avere la maggior parte delle tue impostazioni base.pye poi in altri file devi importare tutto da lì e sovrascrivere alcune opzioni. Ecco come test.pyapparirà il tuo file:

from .base import *

DEBUG = False

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

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

LOGGING = {}

E poi devi specificare l' --settingsopzione come nella risposta @MicroPyramid, o specificare DJANGO_SETTINGS_MODULEla variabile d'ambiente e quindi puoi eseguire i tuoi test:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

Ciao . Dmitrii, grazie per la tua risposta, ho lo stesso caso con questa risposta, ma vorrei avere maggiori indicazioni su come l'app saprà, l'ambiente in cui ci troviamo (test o produzione) , dai un'occhiata al mio ramo, controlla il mio repository github.com/andela/ah-backend-iroquois/tree/develop/authors , come gestirò questa logica?
Lutaaya Huzaifah Idris il

Poiché utilizzo i test noset per eseguire i test, ora come verrà eseguito? Nell'ambiente di test non nell'ambiente di sviluppo
Lutaaya Huzaifah Idris

3

Trovato questo mentre cercavo di correggere alcuni doctests ... Per completezza voglio menzionare che se hai intenzione di modificare le impostazioni quando usi doctests, dovresti farlo prima di importare qualsiasi altra cosa ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

3

Per gli utenti più esigenti .

Il problema più grande è:

  • override_settings non funziona con pytest.
  • La sottoclasse di Django TestCaselo farà funzionare, ma poi non puoi usare dispositivi pytest.

La soluzione è utilizzare il settingsdispositivo qui documentato .

Esempio

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

E nel caso in cui sia necessario aggiornare più campi

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

È possibile ignorare l'impostazione anche per una singola funzione di test.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

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

oppure puoi sovrascrivere l'impostazione per ciascuna funzione della classe.

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

    def test_some_function():
        

1

Sto usando pytest.

Sono riuscito a risolverlo nel modo seguente:

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

Puoi sovrascrivere le impostazioni nel test in questo modo:

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

E se hai bisogno di queste stesse impostazioni in un altro file puoi semplicemente importarlo direttamente test_settings.


0

Se hai più file di test inseriti in una sottodirectory (pacchetto python), puoi sovrascrivere le impostazioni per tutti questi file in base alla condizione di presenza della stringa 'test' in 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)

Non è l'approccio migliore. L'ho usato per cambiare il broker Celery da Redis a Memory.


0

Ho creato un nuovo file settings_test.py che importava tutto dal file settings.py e modificava tutto ciò che è diverso a scopo di test. Nel mio caso volevo utilizzare un bucket di archiviazione cloud diverso durante il test. inserisci qui la descrizione dell'immagine

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.