Django: как создать модель динамически только для тестирования
У меня есть приложение Django, для которого требуется атрибут settings
в форме:
RELATED_MODELS = ('appname1.modelname1.attribute1',
'appname1.modelname2.attribute2',
'appname2.modelname3.attribute3', ...)
Затем перехватывает свой post_save сигнал для обновления некоторой другой фиксированной модели в зависимости от attributeN
.
Я бы хотел протестировать это поведение, и тесты должны работать, даже если это приложение является единственным в проекте (за исключением его собственных зависимостей, другое приложение-оболочка не должно быть установлено). Как я могу создавать и прикреплять/регистрировать/активировать макетные модели только для тестовой базы данных? (или это вообще возможно?)
Решения, которые позволят мне использовать тестовые приборы, были бы большими.
Ответы
Ответ 1
Вы можете поместить свои тесты в подкаталог tests/
приложения (а не файл tests.py
) и включить tests/models.py
только с тестовыми моделями.
Затем укажите тестовый script (пример), который включает в себя tests/
"приложение" в INSTALLED_APPS
. (Это не работает при запуске тестов приложений из реального проекта, у которого не будет приложения для тестов в INSTALLED_APPS
, но мне редко бывает полезно запускать многоразовые тесты приложений из проекта, а Django 1.6+ по умолчанию.)
( ПРИМЕЧАНИЕ. Альтернативный динамический метод, описанный ниже, работает только в Django 1.1+, если подклассы test tec TransactionTestCase
- значительно замедляют ваши тесты и больше не работают в Django 1.7+. Он остался здесь только для исторического интереса, не используйте его.)
В начале ваших тестов (т.е. в методе setUp или в начале набора доктрин) вы можете динамически добавлять "myapp.tests"
к настройке INSTALLED_APPS, а затем делать это:
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
Затем в конце ваших тестов вы должны очистить, восстановив старую версию INSTALLED_APPS и снова очистив кэш приложения.
Этот класс инкапсулирует шаблон, чтобы он не загромождал ваш тестовый код так же.
Ответ 2
@paluh answer требует добавления нежелательного кода в файл без тестирования, и, по моему опыту, решение @carl не работает с django.test.TestCase, который необходим для использования приборов. Если вы хотите использовать django.test.TestCase, вам нужно убедиться, что вы вызываете syncdb перед загрузкой приборов. Для этого требуется переопределить метод _pre_setup (помещать код в метод setUp недостаточно). Я использую собственную версию TestCase, которая позволяет мне добавлять приложения с тестовыми моделями. Он определяется следующим образом:
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
Ответ 3
Это решение работает только для более ранних версий django
(до 1.7
). Вы можете легко проверить свою версию:
import django
django.VERSION < (1, 7)
Оригинальный ответ:
Это довольно странно, но форма меня работает очень просто:
- добавить test.py в приложение, которое вы собираетесь тестировать,
- в этом файле просто определяют тестовые модели,
- ниже введите свой тестовый код (определение doctest или TestCase),
Ниже я поставил некоторый код, который определяет модель статьи, которая необходима только для тестов (она существует в someapp/tests.py, и я могу проверить ее только с помощью:./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>]
"""}
Модульные тесты также работают с таким определением модели.
Ответ 4
Я выбрал несколько иной, хотя и более близкий подход к динамическому созданию моделей только для тестирования.
Я сохраняю все свои тесты в подкаталоге tests
, который живет в моем приложении files
. Файл models.py
в подкаталоге tests
содержит мои тестовые модели. Связанная часть появляется здесь, где мне нужно добавить следующее в мой файл 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',]
Я также установил db_table в моей тестовой модели, потому что иначе Django создал бы таблицу с именем tests_<model_name>
, что могло вызвать конфликт с другими тестовыми моделями в другом приложении. Здесь моя моя тестовая модель:
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'
Ответ 5
Цитата из связанного ответа:
Если вам нужны модели, определенные для тестирования, тогда вы должны проверить Django ticket # 7835, в частности комментарий # 24 часть из которых приведен ниже:
По-видимому, вы можете просто определить модели непосредственно в test.py. Syncdb никогда не импортирует tests.py, поэтому эти модели не будут синхронизироваться с normal db, но они будут синхронизированы с тестовой базой данных и могут быть используемых в тестах.
Ответ 6
Я поделился своим решением, которое я использую в своих проектах. Может быть, это помогает кому-то.
pip install django-fake-model
Два простых шага для создания поддельной модели:
1) Определите модель в любом файле (я обычно определяю модель в тестовом файле около тестового примера)
from django_fake_model import models as f
class MyFakeModel(f.FakeModel):
name = models.CharField(max_length=100)
2) Добавьте декоратор @MyFakeModel.fake_me
в TestCase или в тестовую функцию.
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')
Этот декоратор создает таблицу в вашей базе данных перед каждым тестом и удаляет таблицу после теста.
Также вы можете создать/удалить таблицу вручную: MyFakeModel.create_table()
/MyFakeModel.delete_table()
Ответ 7
Я разработал способ для тестовых моделей для django 1.7 +.
Основная идея: сделайте свое приложение tests
и добавьте tests
в INSTALLED_APPS
.
Вот пример:
$ 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
И у меня разные settings
для разных целей (ссылка: разделение файла настроек), а именно:
-
settings/default.py
: базовый файл настроек
-
settings/production.py
: для производства
-
settings/development.py
: для разработки
-
settings/testing.py
: для тестирования.
И в settings/testing.py
вы можете изменить INSTALLED_APPS
:
settings/testing.py
:
from default import *
DEBUG = True
INSTALLED_APPS += ['common', 'common.tests']
И убедитесь, что вы установили надлежащую метку для своего тестового приложения, а именно
common/tests/apps.py
from django.apps import AppConfig
class CommonTestsConfig(AppConfig):
name = 'common.tests'
label = 'common_tests'
common/tests/__init__.py
, установите правильный AppConfig
(ref: Приложения Django).
default_app_config = 'common.tests.apps.CommonTestsConfig'
Затем сгенерируйте db-миграцию на
python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
Наконец, вы можете запустить тест с параметром --settings=<your_project_name>.settings.testing
.
Если вы используете py.test, вы можете даже удалить файл pytest.ini
вместе с django manage.py
.
py.test
[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing
Ответ 8
Вот шаблон, который я использую для этого.
Я написал этот метод, который я использую в подклассовой версии TestCase. Это происходит следующим образом:
@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
Затем я создаю специальный файл models.py, специфичный для теста, в виде myapp/tests/models.py
, который не включен в INSTALLED_APPS.
В моем методе setUp я вызываю create_models_from_app ('myapp.tests') и создает правильные таблицы.
Единственный "полученный" с этим подходом состоит в том, что вы действительно не хотите создавать модели во время выполнения setUp
, поэтому я улавлю DatabaseError. Я предполагаю, что вызов этого метода может оказаться в верхней части тестового файла, и это будет работать немного лучше.
Ответ 9
Объединяя ваши ответы, особенно @slacy's, я сделал это:
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)
При этом вы не пытаетесь создавать таблицы db более одного раза, и вам не нужно менять INSTALLED_APPS.
Ответ 10
Если вы пишете повторно используемое django-приложение, создайте для него минимальное тестовое приложение!
$ django-admin.py startproject test_myapp_project
$ django-admin.py startapp test_myapp
добавьте как myapp
, так и test_myapp
в INSTALLED_APPS
, создайте свои модели там, и это хорошо!
Я прошел все эти ответы, а также django ticket 7835, и я, наконец, пошел на совершенно другой подход.
Я хотел, чтобы мое приложение (как-то расширило queryset.values ()), чтобы быть в состоянии быть проверено изолированно; Кроме того, мой пакет включает некоторые модели, и я хотел провести чистое различие между тестовыми моделями и пакетами.
Это, когда я понял, было проще добавить в пакет очень маленький проект django!
Это также позволяет намного более четкое разделение кода IMHO:
Там вы можете легко и без каких-либо взломов определить свои модели, и вы знаете, что они будут созданы, когда вы запустите свои тесты там!
Если вы не создаете независимое, многоразовое приложение, вы все равно пойдете следующим образом: создайте приложение test_myapp
и добавьте его в свой INSTALLED_APPS только в отдельном settings_test_myapp.py
!