模拟 Django 存储模型 ImageField 后端 S3

问题描述 投票:0回答:6

我有一个带有 ImageField 的模型,由 django-storages 的 S3Boto 支持。 我对“上传图像”视图进行了测试,但事实上,它将图像上传到 S3 减慢了我的测试套件的速度。

为了加快测试速度,处理此问题的最佳实践是什么? 我应该模拟 S3Boto 吗? 也许有一个内存支持的存储后端非常适合测试(自动清理会很好!)?

django unit-testing mocking
6个回答
21
投票

我也刚刚遇到这个问题。通过使用

dj-inmemorystorage
,我获得了更快的测试速度。

设置此功能的快速方法是在与您的设置相同的文件夹中创建一个

test_settings.py

from settings import *
DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'

...并调用

./manage.py test --settings=project.test_settings
来运行测试。

我的首选方法是设置自定义测试运行程序:

project/test_runner.py

from django.conf import settings
from django.test.runner import DiscoverRunner

class FastTestRunner(DiscoverRunner):
    def setup_test_environment(self):
        super(FastTestRunner, self).setup_test_environment()
        # Don't write files
        settings.DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'
        # Bonus: Use a faster password hasher. This REALLY helps.
        settings.PASSWORD_HASHERS = (
            'django.contrib.auth.hashers.MD5PasswordHasher',
        )

注意: 这也会设置

PASSWORD_HASHER
,因为它显着改善了
User
创建时间
这不应该在生产中设置。

project/settings.py

TEST_RUNNER = 'project.test_runner.FastTestRunner'

要求:

pip install dj-inmemorystorage

更新:从

django-inmemorystorage
更改为
dj-inmemorystorage

更新 2:删除了

django-discover-runner
,因为它现在是 django 中的默认测试运行器,并修复了
PASSWORD_HASHER
相关博客文章的链接。


5
投票

我也遇到了这个问题,所以我想我应该把我的解决方案提出来。 我的解决方案使用

Mock

import mock
from django.core.files.storage import FileSystemStorage
from django.test import TestCase

class ATestCase(TestCase):
    def setUp(self):
        # Stuff Happens

    def tearDown(self):
        # more Stuff

    @mock.patch('storages.backends.s3boto.S3BotoStorage', FileSystemStorage)
    def test_file_stuff(self):
        self.assertMagicPonies(True)

一些问题 - 确保您在设置中进行了合理的

MEDIA_ROOT
设置。 从 django 1.4 开始,您无法使用测试上下文管理器来覆盖
MEDIA_ROOT
,因此您需要为其单独的设置配置 (https://code.djangoproject.com/ticket/17787)
此问题已修复在 1.6 中。 另外,请确保您的 upload_to 在正常文件系统中工作,否则您将收到权限错误。


5
投票

我也使用 S3Boto,但为了测试,我更喜欢自定义设置,其中包括使用文件系统存储。您可以在文件中声明自定义设置,然后可以在测试用例中导入并使用该设置。即使如此,您也可以模拟文件存储,以便文件实际上不会写入磁盘。

这是一个示例

test_settings.py

# myproject/myproject/test_settings.py

from django.test import override_settings

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

用途:


from django.test import TestCase

from myproject.test_settings import common_settings

@common_settings
class MyTestCase(TestCase):
    """Tests go here"""

在模拟文件系统存储时,您可以查看我的答案here on SO


3
投票

我知道这是一个相当古老的线程,但 Django 4.2 现在通过

InMemoryStorage
提供了一种更简单的方法。您只需覆盖设置来声明它,所有媒体文件访问都将在内存中模拟

@override_settings(STORAGES={
    "default": {
        "BACKEND": "django.core.files.storage.memory.InMemoryStorage",
    },
})
class StorageTest(TestCase):
    """Any upload test will only use memory here"""
    ...

0
投票

我建议使用标准 Django 存储进行测试,您可以定义自定义存储路径,并在完成后清理测试套件中的该路径。存储和路径都可以在设置中设置并覆盖以进行测试。


0
投票

在我的例子中,需要使用近乎真实的 S3 后端,因为我与 S3 有其他基于

boto3
的交互。 (Django 5.1,这将来可能会中断)

moto
提供了一套很好的AWS模拟,包括S3。然而,仅仅用
mock_aws
装饰器或上下文管理器包装所有内容是不够的,因为存储是延迟初始化和全局缓存的。

在深入研究 Django 和

django-storages
内部一段时间后,我最终得到了以下
pytest
固定装置(
monkeypatch
是 pytest 的内置装置,
settings
固定装置来自
pytest-django
):

@pytest.fixture
def _mock_aws(monkeypatch, settings):
    import boto3

    def reinit_storages():
        # See https://github.com/getmoto/moto/issues/3446
        # Patch all boto3 uses including django-storages
        from django.core.files import storage

        storage.storages._storages = {}
        storage.default_storage._wrapped = storage.storages["default"]

    with monkeypatch.context() as m:
        m.setenv("AWS_ACCESS_KEY_ID", "testing")
        m.setenv("AWS_SECRET_ACCESS_KEY", "testing")
        m.setenv("AWS_SECURITY_TOKEN", "testing")
        m.setenv("AWS_SESSION_TOKEN", "testing")
        m.setenv("AWS_DEFAULT_REGION", "us-east-1")
        with mock_aws():
            boto3.client("s3").create_bucket(Bucket=settings.AWS_STORAGE_BUCKET_NAME)
            reinit_storages()
            yield

    reinit_storages()

关键部件:

  • 改变环境时重置
    django.core.files.storage.storages._storages
  • 重置
    default_storage
    的内部
    _wrapped
    (您不能直接分配给
    default_storage
    ,因为模型元中的
    FileField
    实例引用了它)
  • 临时将环境设置为不可用的东西(moto 也这样做,但我更喜欢尽可能防止任何意外的 AWS 访问,以防万一)
  • 进入摩托
    mock_aws
  • 创建必要的存储桶(
    moto
    模拟完全空的基础设施,但会记住您的操作)
  • 在清理期间,再次重新初始化存储以避免使用陈旧的环境

此设置似乎模拟和取消模拟所有内容,使用真实 S3 存储桶的集成测试在同一会话中按预期工作,无论是在使用

_mock_aws
进行测试之前还是之后。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.