首页 文章

Django:如何动态创建模型以进行测试

提问于
浏览
57

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

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

然后挂钩他们的post_save信号以更新一些其他固定模型,具体取决于定义的 attributeN .

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

允许我使用测试夹具的解决方案会很棒 .

10 回答

  • 9

    您可以将测试放在应用程序的 tests/ 子目录中(而不是 tests.py 文件),并将 tests/models.py 包含在仅测试模型中 .

    然后提供一个测试运行脚本(example),其中包含您在 INSTALLED_APPS 中的 tests/ "app" . (这并没有 INSTALLED_APPS 中的测试应用程序,但我很少发现从项目运行可重用的应用程序测试很有用,并且默认情况下不会使用Django 1.6 . )

    NOTE :下面描述的替代动态方法仅适用于Django 1.1,如果您的测试用例子类 TransactionTestCase - 这会显着降低您的测试速度 - 并且在Django 1.7中根本不再有效 . 它's left here only for historical interest; don' t使用它 . )

    在测试开始时(即在setUp方法中,或在一组doctests的开头),您可以动态地将 "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)
    

    然后在测试结束时,您应该通过恢复旧版本的INSTALLED_APPS并再次清除应用缓存来进行清理 .

    This class封装了模式,因此它不会使测试代码混乱不堪 .

  • 11

    @ 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
    
  • 9

    此解决方案仅适用于 django 的早期版本(在 1.7 之前) . 您可以轻松查看您的版本:

    import django
    django.VERSION < (1, 7)
    

    原始回复:

    这很奇怪,但形式我的作品非常简单:

    • 将tests.py添加到您要测试的应用中,

    • 在这个文件中只定义测试模型,

    • 下面放了你的测试代码(doctest或TestCase定义),

    下面我给出了一些定义了仅用于测试的Article模型的代码(它存在于someapp / tests.py中,我可以用以下方法测试它:./ manage.py test someapp):

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

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

  • 18

    我分享了我在项目中使用的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 添加到TestCase或测试功能 .

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

  • 50

    我选择了一种稍微不同的,虽然更加耦合的方法来动态创建仅用于测试的模型 .

    我将所有测试保存在 files 应用程序中的 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_table,因为否则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'
    
  • 9

    引自a related answer

    如果您想要仅为测试定义模型,那么您应该查看Django机票#7835,特别是注释#24,其中一部分如下:显然您可以直接在tests.py中定义模型 . Syncdb从不导入tests.py,因此这些模型不会同步到普通数据库,但它们将同步到测试数据库,并可用于测试 .

  • 1

    我已经找到了django 1.7测试模型的方法 .

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

    • settings/default.py :基本设置文件

    • settings/production.py :用于 生产环境

    • settings/development.py :用于开发

    • settings/testing.py :用于测试 .

    settings/testing.py 中,您可以修改 INSTALLED_APPS

    settings/testing.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'
    

    然后,生成db migration by

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

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

    如果你使用py.test,你甚至可以删除一个 pytest.ini 文件和django的 manage.py .

    py.test

    [pytest]
    DJANGO_SETTINGS_MODULE=kungfu.settings.testing
    
  • 10

    这是我用来做这个的模式 .

    我编写了这个方法,我在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
    

    然后,我创建了一个特殊的测试特定的models.py文件,类似于 myapp/tests/models.py ,它不包含在INSTALLED_APPS中 .

    在我的setUp方法中,我调用create_models_from_app('myapp.tests')并创建正确的表 .

    这种方法唯一的"gotcha"是你真的不想创建模型时间 setUp 运行,这就是我捕获DatabaseError的原因 . 我想这个方法的调用可以放在测试文件的顶部,这样可以更好一些 .

  • 3

    结合你的答案,特别是@ slacy's,我这样做了:

    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)
    

    这样,您不会尝试多次创建数据库表,也不需要更改INSTALLED_APPS .

  • 4

    如果您正在编写可重用的django-app, create a minimal test-dedicated app for it

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

    myapptest_myapp 同时添加到 INSTALLED_APPS ,在那里创建模型,这很好!

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

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

    在那里你可以干净利落,没有任何黑客定义你的模型,你知道它们将在你那里运行测试时创建!

    如果您没有编写独立的,可重复使用的应用程序,您仍然可以这样:创建一个 test_myapp 应用程序,并将其添加到您的INSTALLED_APPS中,只能在单独的_1694694中!

相关问题