Python Django:如何为测试动态创建模型
我有一个Django应用程序,它需要一个Python Django:如何为测试动态创建模型,python,django,unit-testing,django-models,mocking,Python,Django,Unit Testing,Django Models,Mocking,我有一个Django应用程序,它需要一个settings属性,格式如下: RELATED_MODELS = ('appname1.modelname1.attribute1', 'appname1.modelname2.attribute2', 'appname2.modelname3.attribute3', ...) 然后根据定义的attributeN钩住它们的post_save信号以更新其他一些固定模型 我想测试这
settings
属性,格式如下:
RELATED_MODELS = ('appname1.modelname1.attribute1',
'appname1.modelname2.attribute2',
'appname2.modelname3.attribute3', ...)
然后根据定义的attributeN
钩住它们的post_save信号以更新其他一些固定模型
我想测试这个行为,即使这个应用程序是项目中唯一的一个,测试也应该有效(除了它自己的依赖项,不需要安装其他包装器应用程序)。如何仅为测试数据库创建和附加/注册/激活模拟模型?(或者有可能吗?)
允许我使用测试夹具的解决方案将非常好。您可以将测试放在应用程序的
tests/
子目录中(而不是tests.py
文件),并在仅测试模型中包含tests/models.py
然后提供一个测试运行脚本(),其中包括已安装的应用程序中的测试/
“应用程序”。(从实际项目运行应用程序测试时,这不起作用,因为实际项目中不会安装中的测试应用程序。\u APPS
,但我发现从项目中运行可重用的应用程序测试很少有用,Django 1.6+默认情况下也没有。)
(注意:如果您的测试用例子类TransactionTestCase
,则下面描述的替代动态方法仅在Django 1.1+中有效,这会显著降低您的测试速度,并且在Django 1.7+中不再有效。此处仅出于历史原因保留该方法;不要使用它。)
在测试开始时(即在安装方法中,或在一组doctest的开始时),您可以动态地将“myapp.tests”
添加到已安装的\u APPS设置中,然后执行以下操作:
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
然后在测试结束时,您应该通过恢复已安装应用的旧版本并再次清除应用缓存来进行清理
封装模式,这样就不会让测试代码变得杂乱无章。此解决方案仅适用于早期版本的django
(在1.7
之前)。您可以轻松检查您的版本:
import django
django.VERSION < (1, 7)
导入django
django.VERSION<(1,7)
原始答复:
这很奇怪,但form me的工作原理非常简单:
类文章(models.Model):
title=models.CharField(最大长度=128)
description=models.TextField()
文档=文档文本字段(模板=lambda i:i.description)
def ___; unicode(自):
返回自己的标题
__测试\={“doctest”:“”
#测试模型
>>>从.tests导入文章
#测试数据
>>>by_two=Article.objects.create(title=“可被二整除”,description=“二四六八”)
>>>by_三=Article.objects.create(title=“可被三整除”,description=“三六九”)
>>>by_four=Article.objects.create(title=“可被四整除”,description=“四八”)
>>>Article.objects.all().search(document='four')
[, ]
>>>Article.objects.all().search(document='three')
[]
"""}
单元测试也可以使用这样的模型定义。@paluh的回答要求将不需要的代码添加到非测试文件中,根据我的经验,@carl的解决方案不适用于使用fixture所需的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
@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
我选择了一种稍微不同的方法,尽管更为耦合,来动态地创建模型以进行测试 我将所有测试保存在
tests
子目录中,该子目录位于我的文件
应用程序中。tests
子目录中的models.py
文件包含仅测试的模型。这里是耦合部分,我需要将以下内容添加到我的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_表,因为否则Django会创建名为tests_uu
的表,这可能会导致与其他应用程序中的其他测试模型发生冲突。以下是我的测试模型:
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'
这是我用来做这件事的模式 我已经编写了这个方法,用于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
@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
@classmethod
def从应用程序创建应用程序模型(cls,应用程序名称):
"""
使用指定的字符串app name手动创建模型(仅用于测试)。
模型从模块“.Models”加载
"""
从django.db导入连接,DatabaseError
从django.db.models.loading导入加载应用程序
应用程序=加载应用程序(应用程序名称)
从django.core.management导入sql
从django.core.management.color导入no_样式
sql=sql.sql\u创建(应用程序,无样式(),连接)
cursor=connection.cursor()
对于sql中的语句:
尝试:
cursor.execute(语句)
除DatabaseError外,excn:
logger.debug(excn.message)
通过
然后,我在类似于myapp/tests/models.py的文件中创建了一个特殊的测试特定的models.py文件,该文件不包含在已安装的应用程序中
在我的设置方法中,我调用create_models_from_app('myapp.tests'),它会创建适当的表
这种方法唯一的“问题”是,您不想在运行setUp
时创建模型,这就是我捕获DatabaseError的原因。我想对这个方法的调用可以放在测试文件的顶部,这样会更好一些 结合您的答案,特别是@slacy的答案,我做到了:
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)
from django_fake_model import models as f
class MyFakeModel(f.FakeModel):
name = models.CharField(max_length=100)
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')
$ 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
from default import *
DEBUG = True
INSTALLED_APPS += ['common', 'common.tests']
from django.apps import AppConfig
class CommonTestsConfig(AppConfig):
name = 'common.tests'
label = 'common_tests'
default_app_config = 'common.tests.apps.CommonTestsConfig'
python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing
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)