J'ai une application Django qui nécessite un attribut settings
sous la forme de:
RELATED_MODELS = ('appname1.modelname1.attribute1',
'appname1.modelname2.attribute2',
'appname2.modelname3.attribute3', ...)
Accroche ensuite son signal post_save pour mettre à jour un autre modèle fixe en fonction de la variable attributeN
définie.
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 d'emballage ne doit être installée). Comment créer et attacher/enregistrer/activer des modèles simulés uniquement pour la base de données de test? (ou est-ce possible?)
Des solutions qui me permettraient d'utiliser des appareils de test seraient formidables.
Vous pouvez placer vos tests dans un sous-répertoire tests/
de l'application (plutôt que dans un fichier tests.py
) et inclure un tests/models.py
avec les modèles testés uniquement.
Ensuite, fournissez un script de test ( exemple ) qui inclut votre tests/
"app" dans INSTALLED_APPS
. (Cela ne fonctionne pas lors de l'exécution de tests d'application à partir d'un projet réel, qui ne contiendra pas l'application de test dans INSTALLED_APPS
, mais je trouve rarement utile d'exécuter des tests d'application réutilisables à partir d'un projet, et Django 1.6+ ne fonctionne pas par défaut. .)
(NOTE: la méthode dynamique alternative décrite ci-dessous ne fonctionne dans Django 1.1+ si vos sous-classes de cas de test TransactionTestCase
- ce qui ralentit considérablement vos tests - et ne fonctionne plus du tout dans Django 1.7+. À gauche ici uniquement pour votre 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 dynamiquement "myapp.tests"
au paramètre INSTALLED_APPS, puis procédez comme suit:
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 devriez nettoyer en restaurant l'ancienne version de INSTALLED_APPS et en effaçant à nouveau le cache de l'application.
Cette classe encapsule le motif pour ne pas encombrer votre code de test autant.
La réponse de @ paluh nécessite l'ajout de code indésirable dans un fichier non-test et, d'après mon expérience, la solution de @ carl ne fonctionne pas avec Django.test.TestCase, nécessaire à l'utilisation de fixtures. Si vous souhaitez utiliser Django.test.TestCase, vous devez vous assurer d’appeler syncdb avant de charger les projecteurs. Cela nécessite de remplacer la méthode _pre_setup (l'insertion du code dans la méthode 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
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 me forme fonctionne très simple modèle:
Ci-dessous, j'ai mis un code qui définit le modèle d'article qui n'est nécessaire que pour les tests (il existe dans someapp/tests.py et je peux le tester avec juste: ./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 de modèle.
J'ai partagé ma solution que j'utilise dans mes projets. Peut-être que ça aide quelqu'un.
pip install Django-fake-model
Deux étapes simples pour créer un faux modèle:
1) Définir un modèle dans n’importe quel fichier (je définis habituellement un modèle dans un fichier de test à proximité d’un scénario de test)
from Django_fake_model import models as f
class MyFakeModel(f.FakeModel):
name = models.CharField(max_length=100)
2) Ajoutez le décorateur @MyFakeModel.fake_me
à votre TestCase ou à la fonction de test.
class MyTest(TestCase):
@MyFakeModel.fake_me
def test_create_model(self):
MyFakeModel.objects.create(name='123')
model = MyFakeModel.objects.get(name='123')
self.assertEqual(model.name, '123')
Ce décorateur crée une table dans votre base de données avant chaque test et supprime la table après le test.
Aussi, vous pouvez créer / supprimer table manuellement: MyFakeModel.create_table()
/MyFakeModel.delete_table()
Citant de une réponse connexe :
Si vous souhaitez que les modèles ne soient définis que pour les tests, vous devez vérifier Ticket Django n ° 7835 en particulier commentaire n ° 24 dont une partie de Est donnée ci-dessous:
Apparemment, vous pouvez simplement définir des modèles directement dans votre tests.py. Syncdb n’importe jamais tests.py, ces modèles ne seront donc pas synchronisés avec la base de données Normale, mais ils seront synchronisés avec la base de données de test, et peut être utilisé dans les tests.
J'ai trouvé un moyen de tester uniquement des modèles pour Django 1.7+.
L'idée de base est de faire de votre tests
une application et d'ajouter votre tests
à INSTALLED_APPS
.
Voici un exemple:
$ ls common
__init__.py admin.py apps.py fixtures models.py pagination.py tests validators.py views.py
$ ls common/tests
__init__.py apps.py models.py serializers.py test_filter.py test_pagination.py test_validators.py views.py
Et j’ai différents settings
à des fins différentes (ref: scinder le fichier de paramètres ), à savoir:
settings/default.py
: fichier de paramètres de basesettings/production.py
: pour la productionsettings/development.py
: pour le développementsettings/testing.py
: pour tester.Et dans settings/testing.py
, vous pouvez modifier INSTALLED_APPS
:
settings/testing.py
:
from default import *
DEBUG = True
INSTALLED_APPS += ['common', 'common.tests']
Et assurez-vous que vous avez défini une étiquette appropriée pour votre application de test, à savoir:
common/tests/apps.py
from Django.apps import AppConfig
class CommonTestsConfig(AppConfig):
name = 'common.tests'
label = 'common_tests'
common/tests/__init__.py
, configurez AppConfig
(réf: Django Applications ).
default_app_config = 'common.tests.apps.CommonTestsConfig'
Ensuite, générez la migration de la base de données par
python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
Enfin, vous pouvez exécuter votre test avec param --settings=<your_project_name>.settings.testing
.
Si vous utilisez py.test, vous pouvez même supprimer un fichier pytest.ini
avec le manage.py
de Django.
py.test
[pytest]
Django_SETTINGS_MODULE=kungfu.settings.testing
J'ai choisi une approche légèrement différente, bien que davantage couplée, de création dynamique de modèles uniquement pour les tests.
Je conserve tous mes tests dans un sous-répertoire tests
qui réside dans mon application files
. Le fichier models.py
du sous-répertoire tests
contient mes modèles uniquement test. La partie couplée vient ici, où je dois ajouter ce qui suit à mon fichier settings.py
:
# 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>
, ce qui pourrait avoir entraîné un conflit avec d'autres modèles de test dans une autre application. Voici mon 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'
Voici le modèle que j'utilise pour faire cela.
J'ai écrit cette méthode que j'utilise sur une version sous-classée de TestCase. Cela va comme suit:
@classmethod
def create_models_from_app(cls, app_name):
"""
Manually create Models (used only for testing) from the specified string app name.
Models are loaded from the module "<app_name>.models"
"""
from Django.db import connection, DatabaseError
from Django.db.models.loading import load_app
app = load_app(app_name)
from Django.core.management import sql
from Django.core.management.color import no_style
sql = sql.sql_create(app, no_style(), connection)
cursor = connection.cursor()
for statement in sql:
try:
cursor.execute(statement)
except DatabaseError, excn:
logger.debug(excn.message)
pass
Ensuite, je crée un fichier models.py spécifique au test, quelque chose comme myapp/tests/models.py
, qui n’est pas inclus dans INSTALLED_APPS.
Dans ma méthode setUp, j'appelle create_models_from_app ('myapp.tests') et crée les tables appropriées.
Le seul "obtenu" avec cette approche est que vous ne voulez pas vraiment créer les modèles à chaque fois que setUp
s'exécute, c'est pourquoi j'attrape DatabaseError. Je suppose que l’appel à cette méthode pourrait aller en haut du fichier de test et que cela fonctionnerait un peu mieux.
En combinant vos réponses, en particulier @ slacy, j'ai fait ceci:
class TestCase(test.TestCase):
initiated = False
@classmethod
def setUpClass(cls, *args, **kwargs):
if not TestCase.initiated:
TestCase.create_models_from_app('myapp.tests')
TestCase.initiated = True
super(TestCase, cls).setUpClass(*args, **kwargs)
@classmethod
def create_models_from_app(cls, app_name):
"""
Manually create Models (used only for testing) from the specified string app name.
Models are loaded from the module "<app_name>.models"
"""
from Django.db import connection, DatabaseError
from Django.db.models.loading import load_app
app = load_app(app_name)
from Django.core.management import sql
from Django.core.management.color import no_style
sql = sql.sql_create(app, no_style(), connection)
cursor = connection.cursor()
for statement in sql:
try:
cursor.execute(statement)
except DatabaseError, excn:
logger.debug(excn.message)
Avec cela, vous n'essayez pas de créer des tables de base de données plus d'une fois, et vous n'avez pas besoin de changer votre INSTALLED_APPS.
Si vous écrivez une application Django réutilisable, créez-lui une application minimale dédiée au test !
$ Django-admin.py startproject test_myapp_project
$ Django-admin.py startapp test_myapp
ajoutez à la fois myapp
et test_myapp
au INSTALLED_APPS
, créez vos modèles ici et c'est bon!
J'ai passé en revue toutes ces réponses ainsi que le billet Django 7835 , et j'ai finalement opté pour une approche totalement différente. Je voulais que mon application (qui étend quelque peu queryset.values ()) puisse être testée de manière isolée; de plus, mon paquet inclut certains modèles et je voulais une distinction nette entre les modèles de test et ceux du paquet.
C'est alors que j'ai réalisé qu'il était plus facile d'ajouter un très petit projet Django dans le paquet! Cela permet également une séparation beaucoup plus nette du code IMHO:
Là-bas, vous pouvez définir vos modèles proprement et sans aucun piratage. Vous savez qu'ils seront créés lors de l'exécution de vos tests!
Si vous n'écrivez pas une application indépendante réutilisable, vous pouvez toujours procéder ainsi: créez une application test_myapp
et ajoutez-la à votre INSTALLED_APPS uniquement dans un settings_test_myapp.py
séparé!
Quelqu'un a déjà mentionné le billet Django # 7835 , mais il semble y avoir une réponse plus récente qui semble beaucoup plus prometteuse pour les versions plus récentes de Django. Spécifiquement # 42 , qui propose une TestRunner
différente:
from importlib.util import find_spec
import unittest
from Django.apps import apps
from Django.conf import settings
from Django.test.runner import DiscoverRunner
class TestLoader(unittest.TestLoader):
""" Loader that reports all successful loads to a runner """
def __init__(self, *args, runner, **kwargs):
self.runner = runner
super().__init__(*args, **kwargs)
def loadTestsFromModule(self, module, pattern=None):
suite = super().loadTestsFromModule(module, pattern)
if suite.countTestCases():
self.runner.register_test_module(module)
return suite
class RunnerWithTestModels(DiscoverRunner):
""" Test Runner that will add any test packages with a 'models' module to INSTALLED_APPS.
Allows test only models to be defined within any package that contains tests.
All test models should be set with app_label = 'tests'
"""
def __init__(self, *args, **kwargs):
self.test_packages = set()
self.test_loader = TestLoader(runner=self)
super().__init__(*args, **kwargs)
def register_test_module(self, module):
self.test_packages.add(module.__package__)
def setup_databases(self, **kwargs):
# Look for test models
test_apps = set()
for package in self.test_packages:
if find_spec('.models', package):
test_apps.add(package)
# Add test apps with models to INSTALLED_APPS that aren't already there
new_installed = settings.INSTALLED_APPS + Tuple(ta for ta in test_apps if ta not in settings.INSTALLED_APPS)
apps.set_installed_apps(new_installed)
return super().setup_databases(**kwargs)