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) 

原始回复:

这很奇怪,但形成我很简单的模式:

  1. 添加tests.py到你要testing的应用程序,
  2. 在这个文件中只是定义了testing模型,
  3. 下面把你的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 

添加myapptest_myappINSTALLED_APPS ,在那里创build你的模型,这是很好的去!

我已经经历了所有这些答案以及django票7835 ,我终于去了一个完全不同的方法。 我想我的应用程序(以某种方式扩展queryset.values())能够被孤立地testing; 另外,我的软件包确实包含了一些模型,我希望在testing模型和软件包之间有一个清晰的区别。

那时候我意识到在软件包中添加一个非常小的django项目会更容易! 这也允许一个更简洁的代码恕我直言:

在那里,你可以干净利落地定义你的模型,而且你知道当你在里面运行你的testing时,他们会被创build。

如果你没有编写一个独立的,可重用的应用程序,你仍然可以这样做:创build一个test_myapp应用程序,并将其添加到INSTALLED_APPS只在一个单独的settings_test_myapp.py