如何在Django中使用不同的设置进行单元测试?

问题描述 投票:95回答:8

是否有任何简单的机制来覆盖单元测试的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()仅用于提供默认值。

django settings testing django-managers
8个回答
147
投票

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

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

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

这些功能在Django 1.3中尚不存在。

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


44
投票

您可以对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值?如果您的测试例程触发了“其他东西”,那么我不确定任何数量的猴子修补都会保存测试而不会使测试本身的真实性失效。


20
投票

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

如果您在测试中经常更改设置并使用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()

18
投票

您可以在运行测试时传递--settings选项

python manage.py test --settings=mysite.settings_local

16
投票

虽然在运行时覆盖设置配置可能会有所帮助,但在我看来,您应该创建一个单独的文件进行测试。这样可以节省大量的测试配置,这可以确保您永远不会做出不可逆转的事情(比如清理登台数据库)。

假设您的测试文件存在于'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


8
投票

如果您的生产和测试环境配置之间没有太多差异,那么@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 = {}

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

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

3
投票

在尝试修复一些doctests时找到了这个...为了完整性我想提一下,如果你在使用doctests时要修改设置,你应该在导入任何其他内容之前做到这一点......

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc

1
投票

我正在使用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
© www.soinside.com 2019 - 2024. All rights reserved.