Django:如何dynamic创build一个模型来进行testing
我有一个Django应用程序需要一个settings
属性的forms:
RELATED_MODELS = ('appname1.modelname1.attribute1', 'appname1.modelname2.attribute2', 'appname2.modelname3.attribute3', ...)
然后钩住他们的post_save信号,根据定义的attributeN
N来更新一些其他的固定模型。
我想testing这种行为和testing应该工作,即使这个应用程序是唯一的项目(除了自己的依赖,没有其他包装应用程序需要安装)。 我怎样才能创build和附加/注册/激活模拟模型只是为了testing数据库? (或者有可能吗?)
允许我使用testing装置的解决scheme将会很好。
您可以将testing放在应用程序的tests/
子目录(而不是tests.py
文件)中,并将tests/models.py
包含在testing专用模型中。
然后在INSTALLED_APPS
中提供包含您的tests/
“应用程序”的testing运行脚本( 示例 )。 (从一个真正的项目运行应用程序testing时,这是行不通的,在INSTALLED_APPS
没有testing应用程序,但是我很less发现从项目运行可重复使用的应用程序testing是有用的,Django 1.6+不是默认的。)
( 注意 :下面介绍的替代dynamic方法只适用于Django 1.1+,如果你的testing用例的子类是TransactionTestCase
(这会显着降低你的testing速度),并且在Django 1.7+中不再有效,它只留给历史使用,不要用它。)
在testing的开始阶段(即在setUp方法中,或者在一系列doctests的开始处),可以dynamic地将"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)
然后在testing结束时,应该通过恢复旧版本的INSTALLED_APPS并重新清除应用程序caching来进行清理。
这个类封装了这个模式,所以它不会让你的testing代码变得非常混乱。
@ paluh的答案要求添加不需要的代码到一个非testing文件,并在我的经验,@ carl的解决scheme不能与django.test.TestCase这是需要使用灯具。 如果你想使用django.test.TestCase,你需要确保在装载之前调用syncdb。 这需要重写_pre_setup方法(将代码放在setUp方法中是不够的)。 我使用我自己的TestCase版本,让我用testing模型添加应用程序。 它被定义如下:
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
这个解决scheme只适用于早期版本的django
(在1.7
之前)。 您可以轻松检查您的版本:
import django django.VERSION < (1, 7)
原始回复:
这很奇怪,但形成我很简单的模式:
- 添加tests.py到你要testing的应用程序,
- 在这个文件中只是定义了testing模型,
- 下面把你的testing代码(doctest或TestCase定义),
下面我已经放了一些定义Article模型的代码,它只是用于testing(它存在于someapp / tests.py中,我可以用./manage.pytestingsomeapp来testing ):
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>] """}
unit testing也使用这种模型定义。
我select了一个稍微不同的,但更耦合的方法来dynamic创build模型只是为了testing。
我将所有的testing保存在我的files
应用程序中的tests
子目录中。 tests
子目录中的models.py
文件包含我的仅testing模型。 耦合部分在这里,我需要添加以下到我的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',]
我也在我的testing模型中设置了db_table,否则Django会创build名为tests_<model_name>
的表,这可能会导致与其他应用程序中的其他testing模型发生冲突。 这是我的testing模型:
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'
从相关的答案引用:
如果你想要为testing定义的模型只有那么你应该检查出Django票#7835特别是评论#24部分是给出如下:
显然你可以直接在你的tests.py中定义模型。 Syncdb永远不会导入tests.py,所以这些模型不会同步到正常的数据库,但是他们会同步到testing数据库,并且可以在testing中使用。
我分享了我在项目中使用的解决scheme 。 也许它有助于某人。
pip install django-fake-model
两个简单的步骤来创build假模型:
1)在任何文件中定义模型(我通常在testing用例附近的testing文件中定义模型)
from django_fake_model import models as f class MyFakeModel(f.FakeModel): name = models.CharField(max_length=100)
2)添加装饰@MyFakeModel.fake_me
到您的TestCase或testingfunction。
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')
这个装饰器在每次testing之前都会在数据库中创build表格,并在testing之后移除表格。
您也可以手动创build / 删除表: MyFakeModel.create_table()
/ MyFakeModel.delete_table()
我已经想出了一个django 1.7+的testing模型的方法。
基本的想法是,让你的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
:进行testing。
在settings/testing.py
,你可以修改INSTALLED_APPS
:
settings/testing.py
:
from default import * DEBUG = True INSTALLED_APPS += ['common', 'common.tests']
并确保你已经为你的testing应用程序设置了一个合适的标签,
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 Applications )。
default_app_config = 'common.tests.apps.CommonTestsConfig'
然后,通过生成db迁移
python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
最后,你可以用param --settings=<your_project_name>.settings.testing
来运行你的testing。
如果你使用py.test,你甚至可以把一个pytest.ini
文件和django的manage.py
文件pytest.ini
一起。
py.test
[pytest] DJANGO_SETTINGS_MODULE=kungfu.settings.testing
这是我用来做这个的模式。
我已经写了一个我在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
然后,我在myapp/tests/models.py
中创build一个特殊的特定于testing的models.py文件,这个文件不包含在INSTALLED_APPS中。
在我的setUp方法中,我调用create_models_from_app('myapp.tests')并创build适当的表。
这种方法唯一的“难题”就是你不想在setUp
运行的时候创build模型,这就是为什么我要捕获DatabaseError的原因。 我想对这个方法的调用可以放在testing文件的顶部,这样会更好一些。
结合你的答案,特别是@ 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)
有了这个,你不要尝试多次创build数据库表,而且你不需要改变你的INSTALLED_APPS。
如果你正在编写一个可重用的Django应用程序, 创build一个最小的testing专用的应用程序 !
$ django-admin.py startproject test_myapp_project $ django-admin.py startapp test_myapp
添加myapp
和test_myapp
到INSTALLED_APPS
,在那里创build你的模型,这是很好的去!
我已经经历了所有这些答案以及django票7835 ,我终于去了一个完全不同的方法。 我想我的应用程序(以某种方式扩展queryset.values())能够被孤立地testing; 另外,我的软件包确实包含了一些模型,我希望在testing模型和软件包之间有一个清晰的区别。
那时候我意识到在软件包中添加一个非常小的django项目会更容易! 这也允许一个更简洁的代码恕我直言:
在那里,你可以干净利落地定义你的模型,而且你知道当你在里面运行你的testing时,他们会被创build。
如果你没有编写一个独立的,可重用的应用程序,你仍然可以这样做:创build一个test_myapp
应用程序,并将其添加到INSTALLED_APPS只在一个单独的settings_test_myapp.py
!