代码之家  ›  专栏  ›  技术社区  ›  muhuk

Django:如何为测试动态创建模型

  •  58
  • muhuk  · 技术社区  · 16 年前

    我有一个Django应用程序需要 settings 属性的形式为:

    RELATED_MODELS = ('appname1.modelname1.attribute1',
                      'appname1.modelname2.attribute2', 
                      'appname2.modelname3.attribute3', ...)
    

    attributeN 定义

    我想测试这个行为,即使这个应用程序是项目中唯一的一个,测试也应该有效(除了它自己的依赖项,不需要安装其他包装器应用程序)。如何仅为测试数据库创建和附加/注册/激活模拟模型?(或者有可能吗?)

    11 回复  |  直到 16 年前
        1
  •  52
  •   Carl Meyer    10 年前

    你可以把你的测试放在一个 tests/ 应用程序的子目录(而不是 tests.py 文件),并包括 tests/models.py 仅测试模型。

    然后提供一个测试运行脚本( example )包括你的 INSTALLED_APPS . (当从实际项目运行应用程序测试时,这不起作用,因为实际项目不会在应用程序中运行测试。) 已安装的应用程序 ,但我很少发现从项目中运行可重用的应用程序测试是有用的,而Django 1.6+在默认情况下并不有用。)

    ( 笔记 TransactionTestCase -这大大降低了测试速度,在Django 1.7+中根本无法工作。它留在这里只是为了历史利益;不要使用它。)

    在测试开始时(即在安装方法中,或在一组doctest的开始),您可以动态添加 "myapp.tests" 转到“已安装的应用程序”设置,然后执行以下操作:

    from django.core.management import call_command
    from django.db.models import loading
    loading.cache.loaded = False
    call_command('syncdb', verbosity=0)
    

    This class

        2
  •  18
  •   djvg Carl Meyer    3 年前

    @paluh的回答要求将不需要的代码添加到非测试文件中,根据我的经验,@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
  •  15
  •   Kirill Ermolov    9 年前

    我分享了我的 solution 我在项目中使用的。也许这对某人有帮助。

    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 给你 或测试功能。

    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()

        4
  •  11
  •   paluh    9 年前

    django 1.7 )。您可以轻松检查您的版本:

    import django
    django.VERSION < (1, 7)
    

    原始答复:

    1. 将tests.py添加到要测试的应用程序,
    2. 下面是您的测试代码(doctest或TestCase定义),

    ./manage.py测试应用程序 ):

    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>]
    """}
    

    单元测试也使用这种模型定义。

        5
  •  11
  •   Xiao Hanyu    9 年前

    我已经为django 1.7+找到了一种只测试模型的方法。

    基本的想法是,让你的 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 出于不同目的(参考: splitting up the settings file ),即:

    • settings/default.py
    • settings/production.py :用于生产
    • settings/development.py :发展
    • settings/testing.py

    而且 设置/测试.py ,您可以修改 已安装的应用程序

    设置/测试.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 (参考: Django Applications

    default_app_config = 'common.tests.apps.CommonTestsConfig'
    

    然后,通过

    python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
    

    最后,您可以使用param运行测试 --settings=<your_project_name>.settings.testing .

    pytest.ini 和django的文件一起归档 manage.py .

    py.test

    [pytest]
    DJANGO_SETTINGS_MODULE=kungfu.settings.testing
    
        6
  •  10
  •   Jashugan    13 年前

    我选择了一种稍微不同的方法,尽管更为耦合,来动态地创建模型以进行测试。

    tests 位于我的 files 应用程序。这个 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_<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'
    
        7
  •  10
  •   Community Neeleshkumar S    7 年前

    a related answer :

    Django ticket #7835 特别地 comment #24 其中一部分 详情如下:

    显然,您可以直接在tests.py中定义模型。 Syncdb从不导入tests.py,因此这些模型不会同步到 正常的数据库,但它们将同步到测试数据库,并且可以 用于测试。

        8
  •  4
  •   slacy    12 年前

    @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 这不包括在已安装的应用程序中。

    在我的设置方法中,我调用create_models_from_app('myapp.tests'),它会创建适当的表。

    setUp 运行,这就是我捕获DatabaseError的原因。我想对这个方法的调用可以放在测试文件的顶部,这样会更好一些。

        9
  •  4
  •   zVictor Cesar Canassa    12 年前

    结合你的回答,特别是@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)
    

        10
  •  1
  •   Stefano    10 年前

    如果您正在编写可重用的django应用程序, 为它创建一个最小的测试专用应用程序 !

    $ django-admin.py startproject test_myapp_project
    $ django-admin.py startapp test_myapp
    

    两者相加 myapp test_myapp INSTALLED_APPS ,在那里创建您的模型,这样做很好!

    7835 ,我最终选择了一种完全不同的方法。 我希望我的应用程序(以某种方式扩展了queryset.values())能够单独测试;另外,我的软件包确实包含一些模型,我希望在测试模型和软件包模型之间有一个清晰的区别。

    那时我意识到在包中添加一个非常小的django项目更容易! 这还允许更清晰地分离代码IMHO:

    在那里,您可以干净地定义您的模型,而无需任何黑客攻击,并且您知道,当您从那里运行测试时,将创建这些模型!

    测试我的应用程序 应用程序,并仅以单独的方式将其添加到已安装的应用程序中 settings_test_myapp.py

        11
  •  0
  •   André Fratelli    6 年前

    有人已经提到了 Django ticket #7835 ,但似乎有一个更为近期的回答,对于Django的最新版本来说,这个回答看起来更有希望。明确地 #42 TestRunner :

    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)