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

Sov*_*iut 104 testing django settings django-managers

是否有任何简单的机制来覆盖单元测试的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]
Run Code Online (Sandbox Code Playgroud)

管理器用于settings.NEWS_LATEST_MAX切片查询集.如果getattr()设置不存在,则仅用于提供默认值.

sli*_*nkp 151

编辑:如果你想改变设置了这样的回答适用于数量的特定测试.

由于Django的1.4,有办法在测试过程中,以覆盖设置: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

测试用例将具有self.settings上下文管理器,并且也将有可应用到任何一个测试方法或整个测试用例的子类的@override_settings装饰器.

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

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

  • 我想说这是现在Django 1.4+中最好的方法 (4认同)
  • 较新版本的Django具有特定的上下文管理器:https://docs.djangoproject.com/en/1.8/topics/testing/tools/#overriding-settings (2认同)
  • 我最喜欢的:`@modify_settings(MIDDLEWARE_CLASSES=...` (谢谢你的回答) (2认同)

Jar*_*die 43

您可以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
Run Code Online (Sandbox Code Playgroud)

但是,由于django测试用例运行单线程,我很好奇还有什么可能修改NUM_LATEST值?如果您的测试例程触发了"其他东西",那么我不确定任何数量的猴子修补都会保存测试而不会使测试本身的真实性失效.


aka*_*ola 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)
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()
Run Code Online (Sandbox Code Playgroud)


小智 20

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

python manage.py test --settings=mysite.settings_local
Run Code Online (Sandbox Code Playgroud)

  • 我认为,如果有人忘记一次添加设置参数,将会很危险。 (3认同)

hsp*_*her 16

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

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

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为这对我来说是一个很好的解决方案。我有太多使用缓存的测试和代码。对我来说,一遍一遍地覆盖设置将很困难。我将创建两个配置文件并确定要使用哪个。MicroPyramid的答案也可用,但是如果我忘记一次添加设置参数,那将很危险。 (2认同)

Dmi*_*lov 9

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

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

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py
Run Code Online (Sandbox Code Playgroud)

因此,您需要将大部分设置放入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 = {}
Run Code Online (Sandbox Code Playgroud)

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

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 
Run Code Online (Sandbox Code Playgroud)


Pit*_*kos 9

对于pytest用户。

最大的问题是:

  • override_settings 不适用于 pytest。
  • 子类化 DjangoTestCase将使其工作,但您不能使用 pytest 固定装置。

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

例子

def test_with_specific_settings(settings):
    settings.DEBUG = False
    settings.MIDDLEWARE = []
    ..
Run Code Online (Sandbox Code Playgroud)

如果您需要更新多个字段

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)
Run Code Online (Sandbox Code Playgroud)


Ase*_*eem 7

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

设置_测试.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'
Run Code Online (Sandbox Code Playgroud)

管理.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)
Run Code Online (Sandbox Code Playgroud)


小智 6

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

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

    @override_settings(SOME_SETTING="some_value")
    def test_some_function():
        
Run Code Online (Sandbox Code Playgroud)

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

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

    def test_some_function():
        
Run Code Online (Sandbox Code Playgroud)