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

时间:2009-02-02 11:35:53

标签: python django unit-testing django-models mocking

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

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

然后根据定义的attributeN挂钩他们的post_save信号以更新其他固定模型。

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

允许我使用测试夹具的解决方案非常棒。

11 个答案:

答案 0 :(得分:51)

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

然后在tests/中提供包含INSTALLED_APPS“app”的测试运行脚本(example)。 (当从真实项目中运行应用程序测试时,这不起作用,实际项目中没有INSTALLED_APPS中的测试应用程序,但我很少发现从项目运行可重用的应用程序测试很有用,而且Django 1.6+没有默认情况下。)

注意:下面描述的替代动态方法仅适用于Django 1.1+,如果您的测试用例子类TransactionTestCase - 这会显着减慢您的测试速度 - 并且根本不再适用Django 1.7+。它留在这里只是出于历史兴趣;不要使用它。)

在测试开始时(即在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封装了模式,因此它不会使测试代码混乱不堪。

答案 1 :(得分:18)

@ 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

答案 2 :(得分:11)

此解决方案仅适用于早期版本的django1.7之前)。您可以轻松查看您的版本:

import django
django.VERSION < (1, 7)

原始回复:

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

  1. 将tests.py添加到您要测试的应用
  2. 在此文件中只定义测试模型,
  3. 下面放了你的测试代码(doctest或TestCase定义),
  4. 下面我介绍了一些定义了仅用于测试的文章模型的代码(它存在于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>]
    """}
    

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

答案 3 :(得分:11)

我分享了我在项目中使用的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()

答案 4 :(得分:9)

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

我将所有测试都放在tests应用中的files子目录中。 models.py子目录中的tests文件包含我的仅测试模型。耦合部分在这里,我需要将以下内容添加到我的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'

答案 5 :(得分:9)

引自a related answer

  

如果您只想为测试定义模型,那么您应该检查   Django ticket #7835特别是comment #24部分   如下:

     
    

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

  

答案 6 :(得分:9)

我已经为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:for production
  • settings/development.py:for development
  • 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'

然后,通过

生成数据库迁移
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

答案 7 :(得分:4)

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

我已经在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')并创建正确的表。

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

答案 8 :(得分: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。

答案 9 :(得分:1)

如果您正在编写可重复使用的django-app,为其创建一个最小的测试专用应用

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

myapptest_myapp添加到INSTALLED_APPS,在那里创建模型,这样做很棒!

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

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

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

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

答案 10 :(得分:0)

有人已经提到Django ticket #7835,但似乎有一个较新的答复,对于较新版本的Django,它似乎更有希望。特别是#42,它提出了一个不同的 $tad = (new TADFactory(['ip'=>'192.168.1.203', 'com_key'=>0]))->get_instance();

TestRunner