如何在Django中干净地单元测试FileField?

78 投票
6 回答
30695 浏览
提问于 2025-04-16 07:37

我有一个模型里面有一个文件字段(FileField)。我想对它进行单元测试。Django的测试框架在管理数据库和邮件方面做得很好。那对于文件字段有没有类似的管理方式呢?

我怎么才能确保这些单元测试不会影响到真实的应用程序呢?

提前谢谢你们!

PS:我的问题几乎和这个Django测试文件字段使用测试数据重复,但那个没有被接受的答案。我只是想再问问这个话题有没有新的进展。

6 个回答

17

我通常在模型中使用 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" 。
现在你只需要在测试的准备阶段,把包含必要文件的测试文件夹复制到“audio”目录下,然后在结束阶段把它删除。

我觉得这不是最干净的方法,但我就是这么做的。

134

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 的一个神奇功能,但在文档中并没有详细说明 :)。不过在 这里 有提到,并且在 这里 实现了。

限制

需要注意的是,你只能在 SimpleUploadedFile 中放入 bytes 类型的数据,因为它的内部实现是用 BytesIO。如果你需要更像文件的真实行为,可以使用 TemporaryUploadedFile

对于 Python 2

如果 你还在用 Python 2,那么在内容中可以省略 b 前缀:

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

有几种方法可以解决这个问题,但都不太好,因为单元测试应该是独立的,而文件操作通常涉及持久的更改。

我的单元测试是在没有生产数据的系统上运行的,所以每次测试后简单地用 git reset --hard 重置上传目录就很方便。这种方法在某种程度上是最好的,因为它不需要改动代码,只要你一开始有好的测试数据,它就能保证有效。

如果你在测试模型的保存方法后不需要对那个文件做任何操作,我建议使用 Python 的优秀 Mock 库 来完全模拟 File 实例(比如 mock_file = Mock(spec=django.core.files.File); mock_file.read.return_value = "假文件内容"),这样你就可以完全避免对文件处理逻辑的更改。Mock 库有几种方法可以在测试方法中 全局替换 Django 的 File 类,这也是最简单的方式。

如果你需要一个真实的文件(比如作为测试的一部分,或者用外部脚本处理等),你可以参考 Mirko 的例子,创建一个 File 对象,并确保它会存储在合适的地方——这里有三种方法可以做到这一点:

  • 让你的测试中的 settings.MEDIA_ROOT 指向一个临时目录(可以查看 Python tempfile 模块的 mkdtemp 函数)。只要你有一个单独的 STATIC_ROOT 用于存放源代码中的媒体文件,这种方法就很好用。
  • 使用自定义的 存储管理器
  • 手动设置每个 File 实例的文件路径,或者使用一个自定义的 upload_to 函数,指向一个在测试设置/清理过程中会被清除的地方,比如 MEDIA_ROOT 下的一个测试子目录。

编辑: mock 对象库在 Python 3.3 版本中是新的。如果你使用的是旧版本的 Python,可以查看 Michael Foord 的版本

撰写回答