69

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

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

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

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

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

4

11 回答 11

54

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

然后提供一个测试运行脚本(示例),其中包含您的tests/“应用程序” INSTALLED_APPS。(这在从实际项目中运行应用程序测试时不起作用,其中没有测试应用程序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 并再次清除应用缓存来进行清理。

此类封装了该模式,因此它不会使您的测试代码非常混乱。

于 2009-02-02T14:49:31.953 回答
19

@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
于 2010-04-20T04:00:49.257 回答
16

我分享了我在项目中使用的解决方案。也许它可以帮助某人。

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

于 2015-09-28T14:17:53.437 回答
11

django此解决方案仅适用于(before 1.7)的早期版本。您可以轻松检查您的版本:

import django
django.VERSION < (1, 7)

原始回复:

这很奇怪,但我的工作方式非常简单:

  1. 将 tests.py 添加到您要测试的应用程序中,
  2. 在这个文件中只定义测试模型,
  3. 下面放置您的测试代码(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>]
"""}

单元测试也适用于此类模型定义。

于 2009-12-01T16:24:59.860 回答
11

我已经找到了一种用于 django 1.7+ 的仅测试模型的方法。

基本思想是,制作tests一个应用程序,然后将您的应用程序添加testsINSTALLED_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: 供测试用。

在 中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
于 2016-01-20T13:08:48.687 回答
10

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

我将所有测试保存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'
于 2012-01-03T22:10:32.480 回答
10

引用相关答案

如果您只想为测试定义模型,那么您应该查看 Django 票 #7835,特别是评论 #24,其中部分如下:

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

于 2013-03-17T12:34:18.393 回答
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 的原因。我想这个方法的调用可以放在测试文件的顶部,这样会更好一些。

于 2012-04-18T21:55:11.757 回答
4

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

于 2012-05-18T18:13:06.933 回答
1

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

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

myapp和都添加test_myapp到 中INSTALLED_APPS,在那里创建您的模型,一切顺利!

我已经阅读了所有这些答案以及 django ticket 7835,最后我采用了一种完全不同的方法。我希望我的应用程序(以某种方式扩展 queryset.values() )能够被单独测试;此外,我的包确实包含一些模型,我希望在测试模型和包模型之间有一个清晰的区别。

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

在那里你可以干净利落地定义你的模型,并且你知道当你从那里运行测试时它们会被创建!

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

于 2014-07-23T10:10:49.130 回答
0

有人已经提到了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)
于 2018-12-27T22:36:42.303 回答