68 votes

Django : Comment créer un modèle de façon dynamique juste pour les tests ?

J'ai une application Django qui nécessite un settings sous la forme d'un attribut :

RELATED_MODELS = ('appname1.modelname1.attribute1',
                  'appname1.modelname2.attribute2', 
                  'appname2.modelname3.attribute3', ...)

Ensuite, le signal post_save est utilisé pour mettre à jour un autre modèle fixe, en fonction de la méthode d'enregistrement. attributeN défini.

Je voudrais tester ce comportement et les tests devraient fonctionner même si cette application est la seule du projet (à l'exception de ses propres dépendances, aucune autre application wrapper ne doit être installée). Comment puis-je créer et attacher/enregistrer/activer des modèles fantaisie uniquement pour la base de données de test ? (ou est-ce tout à fait possible ?)

Des solutions qui me permettent d'utiliser des montages d'essai seraient parfaites.

52voto

Carl Meyer Points 30736

Vous pouvez placer vos tests dans un tests/ de l'application (plutôt qu'un répertoire tests.py ), et inclure un tests/models.py avec les modèles de test seulement.

Fournissez ensuite un script de test ( exemple ) qui comprend votre tests/ "app" dans INSTALLED_APPS . (Cela ne fonctionne pas lorsque vous exécutez des tests d'applications à partir d'un projet réel, qui n'aura pas l'application de tests dans le dossier INSTALLED_APPS mais je trouve rarement utile d'exécuter des tests d'applications réutilisables à partir d'un projet, et Django 1.6+ ne le fait pas par défaut).

( NOTE : La méthode alternative dynamique décrite ci-dessous ne fonctionne dans Django 1.1+ que si votre scénario de test sous-classe TransactionTestCase - qui ralentit considérablement vos tests - et ne fonctionne plus du tout dans Django 1.7+. Il n'est laissé ici que par intérêt historique ; ne l'utilisez pas).

Au début de vos tests (c'est-à-dire dans une méthode setUp, ou au début d'un ensemble de doctests), vous pouvez ajouter de manière dynamique "myapp.tests" au paramètre INSTALLED_APPS, puis faites ceci :

from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)

Ensuite, à la fin de vos tests, vous devez faire le ménage en restaurant l'ancienne version de INSTALLED_APPS et en vidant à nouveau le cache des applications.

Cette classe encapsule le modèle pour qu'il n'encombre pas autant votre code de test.

0 votes

C'est un snipplet propre et puissant (je suppose que c'est le vôtre). Au début, la création d'une application entière me semblait trop importante pour un simple modèle fictif. Mais maintenant, je pense que cela représente mieux l'utilisation réelle du monde du point de vue des tests unitaires. Merci.

1 votes

Ouais, je ne sais pas ce qui est le mieux, mais ça marche pour moi. "Créer une application entière" semble beaucoup moins important quand vous réalisez que tout ce que cela signifie vraiment est "créer un fichier models.py".

0 votes

Carl, merci pour cet extrait. J'étais sur le point d'écrire ceci quand j'ai trouvé cette page et le lien. C'est bien.

18voto

Conley Owens Points 1538

La réponse de @paluh nécessite d'ajouter du code indésirable à un fichier non-test et, d'après mon expérience, la solution de @carl ne fonctionne pas avec django.test.TestCase qui est nécessaire pour utiliser les appareils. Si vous voulez utiliser django.test.TestCase vous devez vous assurer que vous appelez syncdb avant que les montages soient chargés. Pour cela, il faut surcharger la fonction _pre_setup (en plaçant le code dans le setUp n'est pas suffisante). J'utilise ma propre version de TestCase qui me permet d'ajouter des applications avec des modèles de test. Il est défini comme suit :

from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
from django import test

class TestCase(test.TestCase):
    apps = ()

    def _pre_setup(self):
        # Add the models to the db.
        self._original_installed_apps = list(settings.INSTALLED_APPS)
        for app in self.apps:
            settings.INSTALLED_APPS.append(app)
        loading.cache.loaded = False
        call_command('syncdb', interactive=False, verbosity=0)
        # Call the original method that does the fixtures etc.
        super(TestCase, self)._pre_setup()

    def _post_teardown(self):
        # Call the original method.
        super(TestCase, self)._post_teardown()
        # Restore the settings.
        settings.INSTALLED_APPS = self._original_installed_apps
        loading.cache.loaded = False

6 votes

Pour que cela fonctionne avec Sud Je devais passer. migrate=False à call_command.

2 votes

Si vous avez défini settings.INSTALLED_APPS comme un tuple (comme proposé dans la documentation de django), vous devez d'abord le convertir également en liste. Sinon, cela fonctionne bien.

11voto

paluh Points 469

Cette solution ne fonctionne que pour les versions antérieures de django (avant 1.7 ). Vous pouvez vérifier votre version facilement :

import django
django.VERSION < (1, 7)

Réponse originale :

C'est assez étrange, mais pour moi, ça fonctionne de manière très simple :

  1. ajoutez tests.py à l'application que vous allez tester,
  2. dans ce fichier ne font que définir des modèles de test,
  3. en dessous, mettez votre code de test (doctest ou définition de TestCase),

Ci-dessous j'ai mis un peu de code qui définit le modèle Article qui est nécessaire seulement pour les tests (il existe dans someapp/tests.py et je peux le tester juste avec : ./manage.py test someapp ) :

class Article(models.Model):
    title = models.CharField(max_length=128)
    description = models.TextField()
    document = DocumentTextField(template=lambda i: i.description)

    def __unicode__(self):
        return self.title

__test__ = {"doctest": """
#smuggling model for tests
>>> from .tests import Article

#testing data
>>> by_two = Article.objects.create(title="divisible by two", description="two four six eight")
>>> by_three = Article.objects.create(title="divisible by three", description="three six nine")
>>> by_four = Article.objects.create(title="divisible by four", description="four four eight")

>>> Article.objects.all().search(document='four')
[<Article: divisible by two>, <Article: divisible by four>]
>>> Article.objects.all().search(document='three')
[<Article: divisible by three>]
"""}

Les tests unitaires fonctionnent également avec une telle définition du modèle.

0 votes

C'est génial - ça fonctionne bien (j'utilise django 1.2.1) et cela me semble être la "bonne" façon de faire. Le modèle de test doit exister en tant que partie des tests pour cette application.

0 votes

Mise à jour - cela ne fonctionne pas pour les fixtures mais vous pouvez appeler syndb manuellement (via call_command) en surchargeant _pre_setup comme décrit dans la réponse de Conley à cette question.

10voto

joeharrie Points 71

Cita de une réponse connexe :

Si vous souhaitez que les modèles soient définis uniquement à des fins d'essai, vous devriez consulter le site suivant Ticket Django #7835 en particulier commentaire #24 dont une partie est donnée ci-dessous :

Apparemment, vous pouvez simplement définir des modèles directement dans votre test.py. Syncdb n'importe jamais le fichier tests.py, donc ces modèles ne seront pas synchronisés avec l'application normale, mais ils seront synchronisés avec la base de données de test, et pourront être utilisés dans les tests.

1 votes

Cela semble être devenu moins fiable dans Django 1.7+, probablement à cause de la façon dont les migrations sont gérées.

0 votes

@Sarah : pouvez-vous développer ce point ?

3 votes

Django 1.7+ n'a pas de "syncdb". Cela fait au moins un an que je n'ai pas fait de recherches, mais si je me souviens bien, AppConfig.ready() n'est appelé qu'après la construction de la base de données et l'exécution de toutes les migrations, et le module de tests n'est même pas chargé avant AppConfig.ready(). Vous pourriez être en mesure de bidouiller quelque chose avec un exécuteur de tests personnalisé, settings.py, ou AppConfig, mais je n'ai pas été en mesure de faire fonctionner les variantes évidentes de put-the-models-in-the-tests. Si quelqu'un a un exemple de Django 1.7+ où cela fonctionne, je serais heureux de le voir.

10voto

Jashugan Points 122

J'ai choisi une approche légèrement différente, bien que plus couplée, pour créer dynamiquement des modèles juste pour les tests.

Je garde tous mes tests dans un tests qui se trouve dans mon files app. Le site models.py dans le fichier tests Le sous-répertoire contient mes modèles à tester uniquement. C'est ici qu'intervient la partie couplée, où je dois ajouter ce qui suit à mon fichier settings.py fichier :

# check if we are testing right now
TESTING = 'test' in sys.argv

if TESTING:
    # add test packages that have models
    INSTALLED_APPS += ['files.tests',]

J'ai également défini db_table dans mon modèle de test, car sinon Django aurait créé la table avec le nom tests_<model_name> qui peut avoir causé un conflit avec d'autres modèles de test dans une autre application. Voici mon modèle de test :

class Recipe(models.Model):

    '''Test-only model to test out thumbnail registration.'''

    dish_image = models.ImageField(upload_to='recipes/')

    class Meta:
        db_table = 'files_tests_recipe'

0 votes

Cela fonctionnerait bien pour un projet, mais probablement pas pour une application. Une approche propre cependant.

1 votes

C'est vrai. Je pensais que si Django était livré avec la possibilité d'avoir des fichiers de paramétrage dans les applications, alors cela fonctionnerait sans avoir à faire des modifications au niveau du projet.

0 votes

De nombreuses applications prennent en compte les fichiers de configuration des projets. Il y a aussi l'option de quelque chose comme ça : github.com/jaredly/django-appsettings

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X