136

是否有任何简单的机制可以覆盖单元测试的 Django 设置?我的一个模型上有一个管理器,它返回特定数量的最新对象。它返回的对象数量由 NUM_LATEST 设置定义。

如果有人要更改设置,这有可能使我的测试失败。如何覆盖设置setUp()并随后恢复它们tearDown()?如果那不可能,有什么方法可以修改方法或模拟设置吗?

编辑:这是我的经理代码:

class LatestManager(models.Manager):
    """
    Returns a specific number of the most recent public Articles as defined by 
    the NEWS_LATEST_MAX setting.
    """
    def get_query_set(self):
        num_latest = getattr(settings, 'NEWS_NUM_LATEST', 10)
        return super(LatestManager, self).get_query_set().filter(is_public=True)[:num_latest]

管理器用于settings.NEWS_LATEST_MAX对查询集进行切片。getattr()仅用于在设置不存在时提供默认值。

4

13 回答 13

180

编辑:如果您想更改少量特定测试的设置,此答案适用。

从 Django 1.4 开始,有一些方法可以在测试期间覆盖设置: https ://docs.djangoproject.com/en/stable/topics/testing/tools/#overriding-settings

TestCase将有一个self.settings上下文管理器,还有一个@override_settings可以应用于测试方法或整个TestCase子类的装饰器。

这些特性在 Django 1.3 中还不存在。

如果要更改所有测试的设置,则需要为测试创建单独的设置文件,该文件可以加载和覆盖主设置文件中的设置。在其他答案中有几种很好的方法;我已经看到了hspanderdmitrii方法的成功变化。

于 2011-06-20T17:35:09.393 回答
48

你可以对子类做任何你喜欢的事情UnitTest,包括设置和读取实例属性:

from django.conf import settings

class MyTest(unittest.TestCase):
   def setUp(self):
       self.old_setting = settings.NUM_LATEST
       settings.NUM_LATEST = 5 # value tested against in the TestCase

   def tearDown(self):
       settings.NUM_LATEST = self.old_setting

但是,由于 django 测试用例运行单线程,我很好奇还有什么可能会修改 NUM_LATEST 值?如果您的测试例程触发了“其他东西”,那么我不确定任何数量的猴子补丁是否会在不使测试本身的准确性失效的情况下保存测试。

于 2009-05-27T01:50:13.557 回答
28

您可以--settings在运行测试时通过选项

python manage.py test --settings=mysite.settings_local
于 2016-06-10T06:32:29.180 回答
25

尽管在运行时覆盖设置配置可能会有所帮助,但我认为您应该创建一个单独的文件进行测试。这为测试节省了大量配置,这将确保您永远不会做一些不可逆的事情(比如清理暂存数据库)。

假设您的测试文件存在于“my_project/test_settings.py”中,添加

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'

在你的 manage.py 中。这将确保您在运行时python manage.py test仅使用 test_settings。如果您正在使用其他一些测试客户端,例如 pytest,您可以轻松地将其添加到 pytest.ini

于 2015-06-15T13:44:14.183 回答
20

更新:仅在 Django 1.3.x 及更早版本上需要以下解决方案。对于 >1.4,请参阅slinkp 的答案

如果您在测试中经常更改设置并使用 Python ≥2.5,这也很方便:

from contextlib import contextmanager

class SettingDoesNotExist:
    pass

@contextmanager
def patch_settings(**kwargs):
    from django.conf import settings
    old_settings = []
    for key, new_value in kwargs.items():
        old_value = getattr(settings, key, SettingDoesNotExist)
        old_settings.append((key, old_value))
        setattr(settings, key, new_value)
    yield
    for key, old_value in old_settings:
        if old_value is SettingDoesNotExist:
            delattr(settings, key)
        else:
            setattr(settings, key, old_value)

然后你可以这样做:

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()
于 2010-08-19T08:28:12.673 回答
13

@override_settings如果您的生产和测试环境配置之间没有太多差异,那就太好了。

在其他情况下,您最好只使用不同的设置文件。在这种情况下,您的项目将如下所示:

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py

因此,您需要将大部分设置保存在其他文件中base.py,然后在其他文件中导入所有内容,并覆盖一些选项。这是您的test.py文件的外观:

from .base import *

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'app_db_test'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

然后你要么需要--settings在@MicroPyramid 答案中指定选项,要么指定DJANGO_SETTINGS_MODULE环境变量,然后你可以运行你的测试:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 
于 2016-12-27T17:41:26.257 回答
10

对于pytest用户。

最大的问题是:

  • override_settings不适用于 pytest。
  • 对 Django 进行子类TestCase化将使其工作,但是您不能使用 pytest 固定装置。

解决方案是使用此处settings记录的夹具。

例子

def test_with_specific_settings(settings):
    settings.DEBUG = False
    settings.MIDDLEWARE = []
    ..

如果您需要更新多个字段

def override_settings(settings, kwargs):
    for k, v in kwargs.items():
        setattr(settings, k, v)


new_settings = dict(
    DEBUG=True,
    INSTALLED_APPS=[],
)


def test_with_specific_settings(settings):
    override_settings(settings, new_settings)
于 2019-11-07T19:23:28.123 回答
8

您甚至可以为单个测试功能覆盖设置。

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

    @override_settings(SOME_SETTING="some_value")
    def test_some_function():
        

或者您可以覆盖类中每个函数的设置。

@override_settings(SOME_SETTING="some_value")
class SomeTestCase(TestCase):

    def test_some_function():
        
于 2020-07-01T13:20:05.553 回答
3

在尝试修复一些 doctests 时发现了这个...为了完整起见,我想提一下,如果您要在使用 doctests 时修改设置,则应该在导入其他任何内容之前执行此操作...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc
于 2009-12-14T15:26:21.987 回答
3

我创建了一个新的 settings_test.py 文件,该文件将从 settings.py 文件中导入所有内容并修改任何不同的内容以用于测试目的。就我而言,我想在测试时使用不同的云存储桶。 在此处输入图像描述

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

管理.py:

def main():

    # use seperate settings.py for tests
    if 'test' in sys.argv:
        print('using settings_test.py')
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings_test')
    else:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)
于 2020-07-11T23:27:38.933 回答
2

我正在使用pytest。

我设法通过以下方式解决了这个问题:

import django    
import app.setting
import modules.that.use.setting

# do some stuff with default setting
setting.VALUE = "some value"
django.setup()
import importlib
importlib.reload(app.settings)
importlib.reload(modules.that.use.setting)
# do some stuff with settings new value
于 2018-04-10T14:02:03.807 回答
2

您可以通过这种方式覆盖测试中的设置:

from django.test import TestCase, override_settings

test_settings = override_settings(
    DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage',
    PASSWORD_HASHERS=(
        'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    )
)


@test_settings
class SomeTestCase(TestCase):
    """Your test cases in this class"""

如果您在另一个文件中需要这些相同的设置,您可以直接导入test_settings.

于 2019-09-07T06:59:42.350 回答
0

如果您在子目录(python 包)中放置了多个测试文件,您可以根据 sys.argv 中存在“测试”字符串的条件覆盖所有这些文件的设置

app
  tests
    __init__.py
    test_forms.py
    test_models.py

__init__.py:

import sys
from project import settings

if 'test' in sys.argv:
    NEW_SETTINGS = {
        'setting_name': value,
        'another_setting_name': another_value
    }
    settings.__dict__.update(NEW_SETTINGS)

不是最好的方法。用它来将 Celery 代理从 Redis 更改为 Memory。

于 2019-09-27T19:20:31.300 回答