在 django 中单一测试 FileField 的干净方法是什么?

我有一个带 FileField 的模型。我想把它统一起来。Django 测试框架有很好的方法来管理数据库和电子邮件。FileFields 有类似的东西吗?

如何确保单元测试不会污染实际的应用程序?

先谢谢你

PS: 我的问题几乎是 Django 使用测试装置测试 FileField的副本,但它没有一个被接受的答案。只是想再问一下这个话题有没有什么新东西。

27230 次浏览

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

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

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

至于 fixture,我只是在修改 fixture 中的路径之后,将需要的文件复制到测试文件夹中。

例如:。

在一个包含模型的 fixture 中,文件字段指向一个名为“ Audio”的目录,您可以将“ Audio”: “ Audio/audo.wav”替换为“ Audio”: “ Audio/test/audo.wav”。
现在您所要做的就是将测试文件夹和必要的文件复制到 test setUp 中的“ Audio”中,然后在 tearDown 中删除它。

不是我想过的最干净的方式,但我就是干这个的。

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

我的单元测试不运行在带有生产数据的系统上,所以很容易在每次运行后重置上传目录,比如使用 git reset --hard。从某些方面来说,这种方法是最好的,因为它不涉及代码更改,并且只要您从良好的测试数据开始,它就一定能够工作。

如果在测试了模型的保存方法之后,您实际上不需要对该文件做任何事情,我建议使用 python 优秀的 模拟图书馆来完全伪造 File实例(比如类似于 mock_file = Mock(spec=django.core.files.File); mock_file.read.return_value = "fake file contents"的东西) ,这样您就可以完全避免对文件处理逻辑的更改。Mock 库有几种方法可以在测试方法中实现 全球补丁 Django 的 文件类,这种方法非常简单。

如果你需要一个真正的文件(例如,作为测试的一部分,使用外部脚本处理等) ,你可以使用类似于 Mirko 的例子,并在确保它存储在合适的地方之后创建一个 文件对象——这里有三种方法:

  • 让您的测试 settings.MEDIA_ROOT指向一个临时目录(请参阅 Python Tempfile模块的 mkdtemp函数)。只要你有一个类似于单独的 STATIC_ROOT的东西,就可以很好地工作,这个 STATIC_ROOT可以用于作为源代码一部分的媒体文件。
  • 使用自定义 仓库管理员
  • 在每个 File 实例上手动设置文件路径,或者使用自定义的 上传到函数指向测试安装/拆卸过程清除的某个地方,例如 MEDIA_ROOT下的 test 子目录。

编辑: 模拟对象库在 python 版本3.3中是新的。对于旧的 python 版本,请检查 < a href = “ http://www.voidspace.org.uk/python/mock/”rel = “ noReferrer”> Michael Foord 的版本

如果你只想要 创建一个对象需要 FileField 和 不想使用这个领域,那么你可以像这样传递任何(现有的或不存在的)相对路径:

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

然后就可以开始使用了。

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
)

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

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

有一些方法可以模拟仓库。

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

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类,可以更改存储以避免将文件上传到任何服务器