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

Posted

技术标签:

【中文标题】如何在 Django 中使用不同的设置进行单元测试?【英文标题】:How to Unit test with different settings in Django? 【发布时间】:2010-10-29 03:38:28 【问题描述】:

是否有任何简单的机制可以覆盖单元测试的 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() 仅用于在设置不存在时提供默认值。

【问题讨论】:

@Anto -- 你能解释一下原因或提供更好的答案吗? 同时发生了变化;前一个被接受的是this one ;) 【参考方案1】:

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

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

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

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

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

【讨论】:

我想说这是现在在 Django 1.4+ 中最好的方法 您以后如何从测试中访问该设置?我找到的最好的东西是self.settings().wrapped.MEDIA_ROOT,但这太糟糕了。 较新版本的 Django 有一个特定的上下文管理器:docs.djangoproject.com/en/1.8/topics/testing/tools/… 我最喜欢的:@modify_settings(MIDDLEWARE_CLASSES=...(感谢您的回答)【参考方案2】:

您可以对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 值?如果您的测试例程触发了“其他事情”,那么我不确定任何数量的猴子补丁是否会在不破坏测试本身的准确性的情况下保存测试。

【讨论】:

你的例子奏效了。就单元测试的范围以及测试文件中的设置如何通过调用堆栈向下传播而言,这令人大开眼界。 这不适用于settings.TEMPLATE_LOADERS...所以这至少不是一般的方式,设置或Django没有重新加载或任何这个技巧。 这是 Django 早于 1.4 的一个很好的例子。对于 >= 1.4 答案***.com/a/6415129/190127 更正确 使用docs.djangoproject.com/en/dev/topics/testing/tools/… 像这样使用setUp 和tearDown 进行修补是制作非常脆弱的测试的好方法,这些测试比它们需要的更冗长。如果您需要修补类似的东西,请使用 flexmock 之类的东西。 “由于 django 测试用例运行单线程”:在 Django 1.9 中不再是这种情况。【参考方案3】:

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

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

【讨论】:

它停止查找位于 settings.dev 中的应用程序,这是 settings.base 的扩展 我认为如果有人忘记添加一次设置参数会很危险。【参考方案4】:

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

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

【讨论】:

我认为这对我来说是一个很好的解决方案。我有太多使用缓存的测试和代码。我很难一一覆盖设置。我将创建两个配置文件并确定使用哪一个。 MicroPyramid的答案也是可以的,但是如果我忘记添加一次设置参数会很危险。【参考方案5】:

更新:以下解决方案仅在 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()

【讨论】:

这是一个非常酷的解决方案。由于某种原因,我的设置在单元测试中无法正常工作。非常优雅的解决方案,感谢分享。 我正在使用此代码,但我遇到了级联测试失败的问题,因为如果相关测试失败,设置将不会恢复。为了解决这个问题,我在yield 语句周围添加了一个try/finally,函数的最后部分包含在finally 块中,这样设置总是可以恢复的。 我将为后代编辑答案。我希望我做对了! :)【参考方案6】:

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

【讨论】:

你好。 Dmitrii,感谢您的回答与此答案的情况相同,但我想获得更多关于应用程序如何知道的指导,我们所处的环境 (测试或生产),有一个查看我的分支,查看我的 repo github.com/andela/ah-backend-iroquois/tree/develop/authors,我将如何处理该逻辑? 因为我使用 nosetests 来运行测试,现在如何运行呢?,在测试环境而不是在开发环境【参考方案7】:

对于 pytest 用户。

最大的问题是:

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

解决方案是使用记录在here 中的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)

【讨论】:

除非我做错了什么,否则我可以在 pytest 中使用 override_settings 装饰器;我的测试类没有子类TestCase【参考方案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():
        

【讨论】:

【参考方案9】:

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

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

【讨论】:

【参考方案10】:

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

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

ma​​nage.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)

【讨论】:

【参考方案11】:

我正在使用 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

【讨论】:

【参考方案12】:

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

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

【讨论】:

【参考方案13】:

如果您在子目录(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 broker 从 Redis 更改为 Memory。

【讨论】:

以上是关于如何在 Django 中使用不同的设置进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用不同的jest.config.js进行单元和组件测试?

如何对 django 消息进行单元测试?

你如何对 Celery 任务进行单元测试?

如何在 django 中对文件上传进行单元测试

如何知道在 django 中运行单元测试的环境适用于哪个环境

如何对 Django South“数据迁移”进行单元测试