在 django 中对 FileField 进行单元测试的干净方法是什么?

发布于 2024-10-04 12:06:45 字数 319 浏览 2 评论 0原文

我有一个带有 FileField 的模型。我想对其进行单元测试。 django 测试框架有很好的方法来管理数据库和电子邮件。 FileFields 有类似的东西吗?

我如何确保单元测试不会污染真正的应用程序?

提前致谢

PS:我的问题几乎是 使用测试装置的 Django test FileField 但它没有公认的答案。只是想再次询问这个主题是否有新内容。

I have a model with a FileField. I want to unittest it. django test framework has great ways to manage database and emails. Is there something similar for FileFields?

How can I make sure that the unittests are not going to pollute the real application?

Thanks in advance

PS: My question is almost a duplicate of Django test FileField using test fixtures but it doesn't have an accepted answer. Just want to re-ask if something new on this topic.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

少女的英雄梦 2024-10-11 12:06:45

Django 提供了一个很好的方法来做到这一点 - 使用 SimpleUploadedFileTemporaryUploadedFile。如果您需要存储的只是一些哨兵数据,那么 SimpleUploadedFile 通常是更简单的选择:

from django.core.files.uploadedfile import SimpleUploadedFile

my_model.file_field = SimpleUploadedFile(
    "best_file_eva.txt",
    b"these are the file contents!"   # note the b in front of the string [bytes]
)

这是 django 的神奇功能之一,但不会在文档中显示:)。然而,它被引用这里 并在此处实现。

限制

请注意,您只能将 bytes 放入 SimpleUploadedFile 中,因为它是在幕后使用 BytesIO 实现的。如果您需要更真实的、类似文件的行为,您可以使用 TemporaryUploadedFile。

对于 Python 2

如果您被困在 python 2 上,请跳过内容中的 b 前缀:

my_model.file_field = SimpleUploadedFile(
    "best_file_eva.txt",
    "these are the file contents!" # no b
)

Django provides a great way to do this - use a SimpleUploadedFile or a TemporaryUploadedFile. SimpleUploadedFile is generally the simpler option if all you need to store is some sentinel data:

from django.core.files.uploadedfile import SimpleUploadedFile

my_model.file_field = SimpleUploadedFile(
    "best_file_eva.txt",
    b"these are the file contents!"   # note the b in front of the string [bytes]
)

It's one of django's magical features-that-don't-show-up-in-the-docs :). However it is referred to here and implemented here.

Limitations

Note that you can only put bytes in a SimpleUploadedFile since it's implemented using BytesIO behind the scenes. If you need more realistic, file-like behavior you can use TemporaryUploadedFile.

For Python 2

If you're stuck on python 2, skip the b prefix in the content:

my_model.file_field = SimpleUploadedFile(
    "best_file_eva.txt",
    "these are the file contents!" # no b
)
默嘫て 2024-10-11 12:06:45

有几种方法可以解决这个问题,但它们都很丑陋,因为单元测试应该是隔离的,但文件都是关于持久更改的。

我的单元测试不在具有生产数据的系统上运行,因此每次运行后使用 git reset --hard 之类的东西重置上传目录很容易。从某些方面来说,这种方法是最好的,因为它不涉及任何代码更改,并且只要您从良好的测试数据开始,就保证可以工作。

如果在测试模型的保存方法后实际上不需要对该文件执行任何操作,我建议使用 python 的优秀 模拟库 来完全伪造 File 实例(即类似于 mock_file = Mock(spec=django.core.files.File); mock_file .read.return_value =“假文件内容”),这样您就可以完全避免更改文件处理逻辑。 Mock 库有多种方法全局修补 Django 的 File 类 在测试方法中,就像这样简单会得到。

如果您需要一个真实的文件(即作为测试的一部分,使用外部脚本进行处理等),您可以使用类似于 Mirko 示例的内容并创建一个 文件对象 确保将其存储在适当的位置后 - 以下是三种方法:

编辑:模拟对象库是Python 3.3版本中的新功能。对于较旧的 python 版本,请检查 Michael Foord 的版本

There are several ways you could tackle this but they're all ugly since unit tests are supposed to be isolated but files are all about durable changes.

My unit tests don't run on a system with production data so it's been easy to simply reset the upload directory after each run with something like git reset --hard. This approach is in some ways the best simply because it involves no code changes and is guaranteed to work as long as you start with good test data.

If you don't actually need to do anything with that file after testing your model's save method, I'd recommend using python's excellent Mock library to completely fake the File instance (i.e. something like mock_file = Mock(spec=django.core.files.File); mock_file.read.return_value = "fake file contents") so you can completely avoid changes to your file handling logic. The Mock library has a couple of ways to globally patch Django's File class within a test method which is about as easy as this will get.

If you need to have a real file (i.e. for serving as part of a test, processing with an external script, etc.) you can use something similar to Mirko's example and create a File object after making sure it'll be stored somewhere appropriate - here are three ways to do that:

  • Have your test settings.MEDIA_ROOT point to a temporary directory (see the Python tempfile module's mkdtemp function). This works fine as long as you have something like a separate STATIC_ROOT which you use for the media files which are part of your source code.
  • Use a custom storage manager
  • Set the file path manually on each File instance or have a custom upload_to function to point somewhere which your test setup/teardown process purges such as a test subdirectory under MEDIA_ROOT.

Edit: mock object library is new in python version 3.3. For older python versions check Michael Foord's version

骑趴 2024-10-11 12:06:45

我通常使用 doctest 测试模型中的文件字段,

>>> from django.core.files import File
>>> s = SimpleModel()
>>> s.audio_file = File(open("media/testfiles/testaudio.wav"))
>>> s.save()
>>> ...
>>> s.delete()

如果需要,我还会使用测试客户端测试文件上传。

至于固定装置,我只需在修改固定装置中的路径后将所需的文件复制到测试文件夹中即可。

例如,

在包含文件字段指向名为“audio”的目录的模型的装置中,将“audio”:“audio/audio.wav”替换为“audio”:“audio/test/audio.wav”。
现在您所要做的就是将测试文件夹以及必要的文件复制到测试设置中的“音频”中,然后在拆卸中将其删除。

这不是我认为的最干净的方式,但这就是我所做的。

I normally test filefields in models using doctest

>>> from django.core.files import File
>>> s = SimpleModel()
>>> s.audio_file = File(open("media/testfiles/testaudio.wav"))
>>> s.save()
>>> ...
>>> s.delete()

If I need to I also test file uploads with test clients.

As for fixtures, I simply copy the files i need in a test folder, after modifying the paths in the fixture.

e.g.

In a fixture containing models with filefiels pointing to a directory named "audio", you replace "audio": "audio/audio.wav" with "audio": "audio/test/audio.wav" .
Now all you have to do is copy the test folder, with the necessary files, in "audio" in the test setUp and then delete it in tearDown.

Not the cleanest way ever i think, but that's what i do.

风筝在阴天搁浅。 2024-10-11 12:06:45

如果您只想创建一个需要 FileField 的对象并且不想使用此字段,那么您只需传递任何(现有或不存在)相对路径,如下所示:

example_object = models.ExampleModel({'file': "foo.bar"})
example_object.save()

然后就可以使用了。

If you just want to create an object that requires FileField and don't want to use this field then you can just pass any (existing or not) relative path like this:

example_object = models.ExampleModel({'file': "foo.bar"})
example_object.save()

Then it's ready for use.

记忆里有你的影子 2024-10-11 12:06:45

我想最简单的方法是使用 ContentFile 类:

file = ContentFile('text', 'name')
my_model = MyModel()
my_model.file = file
my_model.save()

I guess that easiest way is to use ContentFile class:

file = ContentFile('text', 'name')
my_model = MyModel()
my_model.file = file
my_model.save()
悟红尘 2024-10-11 12:06:45

有一些方法可以模拟存储。

阅读 django 源代码后,以下是我的实现,以避免将文件上传到 s3,无论使用默认存储还是在文件字段或图像字段中分配存储。

from unittest.mock import patch
from django.db.models.fields.files import FileField, FieldFile, ImageField, ImageFieldFile
from django.core.files.storage import FileSystemStorage
from rest_framework.test import APITestCase


class CustomFileSystemStorage(FileSystemStorage):
    def url(self, *args, **kwargs):
        return self.path(*args, **kwargs)


class CustomFieldFile(FieldFile):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.storage = CustomFileSystemStorage()
        
        
class CustomImageFieldFile(ImageFieldFile):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.storage = CustomFileSystemStorage()


class PatchMeta(type):
    """A metaclass to patch all inherited classes."""

    def __new__(meta, name, bases, attrs):
        cls = type.__new__(meta, name, bases, attrs)
        cls = patch.object(ImageField, 'attr_class', CustomImageFieldFile)(cls)
        cls = patch.object(FileField, 'attr_class', CustomFieldFile)(cls)
        return cls


class CustomBaseTestCase(APITestCase, metaclass=PatchMeta):
    """You can inherit this class to do any testcase for mocking storage"""
    pass

通过继承CustomBaseTestCase类,您可以更改存储以避免将文件上传到任何服务器

There is some way to mock storage.

After reading django source code, the below is my implementation to avoid uploading file to s3 regardless of using default storage or assigning the storage in filefield or imagefield.

from unittest.mock import patch
from django.db.models.fields.files import FileField, FieldFile, ImageField, ImageFieldFile
from django.core.files.storage import FileSystemStorage
from rest_framework.test import APITestCase


class CustomFileSystemStorage(FileSystemStorage):
    def url(self, *args, **kwargs):
        return self.path(*args, **kwargs)


class CustomFieldFile(FieldFile):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.storage = CustomFileSystemStorage()
        
        
class CustomImageFieldFile(ImageFieldFile):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.storage = CustomFileSystemStorage()


class PatchMeta(type):
    """A metaclass to patch all inherited classes."""

    def __new__(meta, name, bases, attrs):
        cls = type.__new__(meta, name, bases, attrs)
        cls = patch.object(ImageField, 'attr_class', CustomImageFieldFile)(cls)
        cls = patch.object(FileField, 'attr_class', CustomFieldFile)(cls)
        return cls


class CustomBaseTestCase(APITestCase, metaclass=PatchMeta):
    """You can inherit this class to do any testcase for mocking storage"""
    pass

With inheriting CustomBaseTestCase class, you can change your storage to avoid uploading files to any server

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文