web-dev-qa-db-fra.com

Comment effectuer un test unitaire avec différents paramètres dans Django?

Existe-t-il un mécanisme simple pour remplacer les paramètres Django pour un test unitaire? J'ai un gestionnaire sur l'un de mes modèles qui renvoie un nombre spécifique des derniers objets. Le nombre d'objets qu'il renvoie est défini par un paramètre NUM_LATEST.

Cela a le potentiel de faire échouer mes tests si quelqu'un devait changer le paramètre. Comment puis-je remplacer les paramètres sur setUp() et les restaurer ensuite sur tearDown()? Si ce n'est pas possible, y a-t-il un moyen de corriger la méthode ou de se moquer des paramètres?

EDIT: Voici mon code 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]

Le gestionnaire utilise settings.NEWS_LATEST_MAX Pour découper le jeu de requêtes. La getattr() est simplement utilisée pour fournir une valeur par défaut si le paramètre n'existe pas.

102
Soviut

EDIT: Cette réponse s'applique si vous souhaitez modifier les paramètres d'un petit nombre de spécifique tests.

Depuis Django 1.4, il existe des moyens de remplacer les paramètres pendant les tests: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase aura un gestionnaire de contexte self.settings, et il y aura également un décorateur @override_settings qui peut être appliqué à une méthode de test ou à une sous-classe TestCase entière.

Ces fonctionnalités n'existaient pas encore dans Django 1.3.

Si vous souhaitez modifier les paramètres de tous vos tests, vous souhaiterez créer un fichier de paramètres distinct pour le test, qui peut charger et remplacer les paramètres de votre fichier de paramètres principal. Il existe plusieurs bonnes approches à ce sujet dans les autres réponses; J'ai vu des variantes réussies des approches hspander et dmitrii .

147
slinkp

Vous pouvez faire tout ce que vous voulez dans la sous-classe UnitTest, y compris définir et lire les propriétés d'instance:

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

Étant donné que les cas de test Django exécutent un seul thread, cependant, je suis curieux de savoir quoi d'autre pourrait modifier la valeur NUM_LATEST? Si ce "quelque chose d'autre" est déclenché par votre routine de test, alors je Je ne suis pas sûr qu'une quelconque correction de singe sauvera le test sans invalider la véracité des tests eux-mêmes.

44
Jarret Hardie

Mise à jour : la solution ci-dessous n'est nécessaire que sur Django 1.3.x et versions antérieures. Pour> 1.4 voir réponse de slinkp .

Si vous modifiez fréquemment les paramètres dans vos tests et utilisez Python ≥2,5, cela est également pratique:

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)

Ensuite, vous pouvez faire:

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

Tu peux passer --settings option lors de l'exécution des tests

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

Bien que le remplacement de la configuration des paramètres à l'exécution puisse aider, à mon avis, vous devez créer un fichier distinct pour les tests. Cela permet d'économiser beaucoup de configuration pour les tests et cela garantirait que vous ne finissiez jamais par faire quelque chose d'irréversible (comme le nettoyage de la base de données de transfert).

Supposons que votre fichier de test existe dans 'my_project/test_settings.py', ajoutez

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

dans votre manage.py. Cela garantira que lorsque vous exécutez python manage.py test vous utilisez uniquement les paramètres de test. Si vous utilisez un autre client de test comme pytest, vous pouvez aussi facilement l'ajouter à pytest.ini

16
hspandher

@override_settings est idéal si vous n'avez pas beaucoup de différences entre vos configurations d'environnement de production et de test.

Dans d'autres cas, vous feriez mieux d'avoir simplement des fichiers de paramètres différents. Dans ce cas, votre projet ressemblera à ceci:

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

Vous devez donc disposer de la plupart de vos paramètres dans base.py, puis dans d'autres fichiers, vous devez tout importer à partir de là et remplacer certaines options. Voici ce que votre test.py le fichier ressemblera à:

from .base import *

DEBUG = False

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

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

LOGGING = {}

Et puis vous devez soit spécifier --settings comme dans la réponse @MicroPyramid, ou spécifiez Django_SETTINGS_MODULE variable d'environnement et vous pouvez ensuite exécuter vos tests:

export Django_SETTINGS_MODULE=settings.test
python manage.py test 
8
Dmitrii Mikhailov

J'ai trouvé cela en essayant de corriger certains doctests ... Pour être complet, je veux mentionner que si vous allez modifier les paramètres lors de l'utilisation de doctests, vous devez le faire avant d'importer autre chose ...

>>> from Django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from Django.core.paginator import Paginator
>>> # etc
3
Jiaaro

J'utilise pytest.

J'ai réussi à résoudre ce problème de la manière suivante:

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
Brontes