70

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

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

提前致谢

PS:我的问题几乎是使用测试夹具的 Django 测试 FileField的重复,但它没有可接受的答案。只是想重新询问有关此主题的新内容。

4

5 回答 5

122

Django 提供了一个很好的方法来做到这一点 - 使用 aSimpleUploadedFileTemporaryUploadedFile. 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输入 a,SimpleUploadedFile因为它是BytesIO在幕后使用的。如果您需要更真实的、类似文件的行为,您可以使用TemporaryUploadedFile.

对于 Python 2

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

my_model.file_field = SimpleUploadedFile(
    "best_file_eva.txt",
    "these are the file contents!" # no b
)
于 2013-12-11T01:06:30.290 回答
50

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

我的单元测试不在具有生产数据的系统上运行,因此在每次运行后使用类似git reset --hard. 这种方法在某些方面是最好的,因为它不涉及代码更改,并且只要您从良好的测试数据开始就可以保证工作。

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

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

  • 将您的测试settings.MEDIA_ROOT点指向一个临时目录(请参阅 Python tempfile模块的mkdtemp函数)。只要您有一个单独的东西STATIC_ROOT用于作为源代码一部分的媒体文件,这就可以正常工作。
  • 使用自定义存储管理器
  • 在每个 File 实例上手动设置文件路径,或使用自定义upload_to函数指向您的测试设置/拆卸过程清除的某个位置,例如MEDIA_ROOT.

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

于 2010-12-28T15:41:35.053 回答
15

我通常使用 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”。
现在您所要做的就是在测试设置的“音频”中复制包含必要文件的测试文件夹,然后在拆解中将其删除。

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

于 2010-11-26T17:08:04.343 回答
3

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

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

然后就可以使用了。

于 2012-03-16T14:03:52.727 回答
1

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

file = ContentFile('text', 'name')
my_model = MyModel()
my_model.file = file
my_model.save()
于 2020-09-03T11:59:23.017 回答