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

是否存在为单元测试覆盖 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()仅用于提供默认设置。

93468 次浏览

您可以对 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 值?如果这个“其他东西”是由您的测试例程触发的,那么我不确定任何数量的修补程序都能在不使测试本身失效的情况下保存测试。

在试图修复一些 doctest 时发现了这个问题... 为了完整起见,我想提一下,如果您要在使用 doctest 时修改设置,那么您应该在导入任何其他内容之前进行修改..。

>>> from django.conf import settings


>>> settings.SOME_SETTING = 20


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

更新 : 下面的解决方案只需要在 Django 1.3. x 及更早版本上使用。

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

编辑: 如果您想更改 很小数量的 具体点测试的设置,这个答案适用。

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

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

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

如果要更改测试的 所有设置,则需要为测试创建一个单独的设置文件,该文件可以从主设置文件加载和重写设置。在其他答案中有几种很好的方法来解决这个问题; 我已经看到了 Hspander 综合症Dmitrii 的方法的成功变化。

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

假设您的测试文件存在于‘ my _ project/test _ setings.py’中,添加

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

你的经纪人 Py。这将确保在运行 python manage.py test时仅使用 test _ sets。如果您正在使用其他测试客户机,比如 pytest,那么您可以轻松地将它添加到 pytest.ini

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

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

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

我用的是派斯特。

我设法用以下方法解决了这个问题:

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

您可以通过以下方式重写 test 中的设置:

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

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

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)

不是最好的方法,用它把芹菜代理从 Redis 改为 Memory。

对于 派特用户。

最大的问题是:

  • override_settings对 pytest 不起作用。
  • 子类化 Django 的 TestCase可以让它工作,但是不能使用 pytest fixture。

解决方案是使用 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)

即使对于单个测试函数,也可以重写设置。

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

我创建了一个新的 set _ test.py 文件,它将从 setings.py 文件中导入所有内容,并修改所有不同的内容以便进行测试。 在我的例子中,我希望在测试时使用不同的云存储桶。 enter image description here

Settings _ test. py:

from project1.settings import *
import os


CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

Manage.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)

一个 testCase 中所有测试的设置

class TestSomthing(TestCase):
def setUp(self, **kwargs):
with self.settings(SETTING_BAR={ALLOW_FOO=True})
yield


覆盖 testCase 中的一个设置

from django.test import override_settings

    @override_settings(SETTING_BAR={ALLOW_FOO=False})
def i_need_other_setting(self):
...

很重要


即使您正在覆盖这些设置 这将不适用于您的服务器初始化东西的设置,因为它已经被初始化了,要做到这一点,您将需要使用另一个设置模块启动 django。