Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/306.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Django:如何为测试动态创建模型_Python_Django_Unit Testing_Django Models_Mocking - Fatal编程技术网

Python 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信号以更新其他一些固定模型 我想测试这

我有一个Django应用程序,它需要一个
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的工作原理非常简单:

  • 将tests.py添加到要测试的应用程序
  • 在这个文件中,只需定义测试模型
  • 下面是您的测试代码(doctest或TestCase定义)
  • 下面我放了一些代码,这些代码定义了只用于测试的文章模型(它存在于someapp/tests.py中,我可以使用:./manage.py test someapp对其进行测试):

    类文章(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)