【问题标题】:Django test FileField using test fixturesDjango 使用测试夹具测试 FileField
【发布时间】:2011-01-17 00:19:33
【问题描述】:

我正在尝试为某些具有 FileField 的模型构建测试。模型如下所示:

class SolutionFile(models.Model):
    '''
    A file from a solution.
    '''
    solution = models.ForeignKey(Solution)
    file = models.FileField(upload_to=make_solution_file_path)

我遇到了两个问题:

  1. 使用./manage.py dumpdata 将数据保存到夹具时,不保存文件内容,仅将文件名保存到夹具中。虽然我发现这是预期的行为,因为文件内容没有保存到数据库中,但我想以某种方式将此信息包含在夹具中以进行测试。

  2. 我有一个上传文件的测试用例,如下所示:

    def test_post_solution_file(self):
        import tempfile
        import os
        filename = tempfile.mkstemp()[1]
        f = open(filename, 'w')
        f.write('These are the file contents')
        f.close()
        f = open(filename, 'r')
        post_data = {'file': f}
        response = self.client.post(self.solution.get_absolute_url()+'add_solution_file/', post_data,
                                    follow=True)
        f.close()
        os.remove(filename)
        self.assertTemplateUsed(response, 'tests/solution_detail.html')
        self.assertContains(response, os.path.basename(filename))
    

虽然此测试运行良好,但完成后会将上传的文件留在媒体目录中。当然,删除可以在tearDown()中处理,但我想知道Django是否有另一种处理方式。

我想到的一个解决方案是使用不同的媒体文件夹进行测试,该文件夹必须与测试夹具保持同步。运行测试时,有没有办法在settings.py 中指定另一个媒体目录?我可以在转储数据中加入某种挂钩,以便同步媒体文件夹中的文件吗?

那么,是否有更 Pythonic 或 Django 特定的方式来处理涉及文件的单元测试?

【问题讨论】:

  • 所以 os.remove(foo) 不起作用?它会抛出异常吗?也许该目录/文件上没有正确的权限,无法从您的单元测试中删除它?
  • 代码中的os.remove() 部分从临时目录中删除文件。为了删除上传的文件,我必须查看媒体目录并遵循更复杂的逻辑来找到文件的确切位置。我正在寻找一种更简单、更自动化的方法来实现它,如果它存在的话。
  • 噢,对不起!我看错了你的帖子。如何在你的测试设置中破解 settings.MEDIA_ROOT = '/path/to/project/static/and/then/alternative/storage/' 和 settings.MEDIA_URL = '/static/and/then/alternative/storage/' ?哈克,但可以完成这项工作......
  • 是否可以编写自定义存储实现并在测试期间替换它?看起来应该很容易编写一个只将上传的文件存储在内存中的实现。我不确定在测试期间如何替换它。

标签: python django unit-testing django-testing filefield


【解决方案1】:

Django 提供了一种在 FileFields 上编写测试的好方法,而无需在真实的文件系统中搞砸——使用 SimpleUploadedFile。

from django.core.files.uploadedfile import SimpleUploadedFile

my_model.file_field = SimpleUploadedFile('best_file_eva.txt', b'these are the contents of the txt file')

这是 django 的神奇功能之一——在文档中不显示 :)。但是它被称为here。

【讨论】:

  • 非二进制内容在 Python 3+ 中引发错误;您可以通过简单地使内容二进制来解决这个问题,如下所示:my_model.file_field = SimpleUploadedFile('best_file_eva.txt', b'these are the file contents!')
【解决方案2】:

您可以使用@override_settings() 装饰器as documented 覆盖测试的MEDIA_ROOT 设置:

from django.test import override_settings


@override_settings(MEDIA_ROOT='/tmp/django_test')
def test_post_solution_file(self):
  # your code here

【讨论】:

    【解决方案3】:

    我之前已经为整个图库应用程序编写了单元测试,对我来说效果很好的是使用 python tempfile 和 shutil 模块在临时目录中创建测试文件的副本,然后将它们全部删除。

    以下示例不工作/不完整,但应该让您走上正确的道路:

    import os, shutil, tempfile
    
    PATH_TEMP = tempfile.mkdtemp(dir=os.path.join(MY_PATH, 'temp'))
    
    def make_objects():
        filenames = os.listdir(TEST_FILES_DIR)
    
        if not os.access(PATH_TEMP, os.F_OK):
            os.makedirs(PATH_TEMP)
    
        for filename in filenames:
            name, extension = os.path.splitext(filename)
            new = os.path.join(PATH_TEMP, filename)
            shutil.copyfile(os.path.join(TEST_FILES_DIR, filename), new)
    
            #Do something with the files/FileField here
    
    def remove_objects():
        shutil.rmtree(PATH_TEMP)
    

    我在单元测试的 setUp() 和 tearDown() 方法中运行这些方法,效果很好!您有一个干净的文件副本来测试可重用和可预测的文件字段。

    【讨论】:

    • 我不明白这对我有什么帮助。我想用一个测试覆盖 Django 的媒体目录。而且我还想在使用./manage.py dumpdata时以某种方式导出/复制文件。
    • 覆盖 django 的媒体目录是个坏主意。除非您将当前媒体目录移至其他位置并在之后将其放回原处,否则您将永远无法在您的实时站点上运行测试,因为这将是一项破坏性操作。您可以按照我上面的建议使用shutils 移动媒体文件夹并将其放回原处。除此之外,dumpdata 永远不会为您导出文件。您必须为此编写自己的脚本或 manage.py 扩展名。
    【解决方案4】:

    使用 pytest 和 pytest-django,我在 conftest.py 文件中使用它:

    import tempfile
    import shutil
    from pytest_django.lazy_django import skip_if_no_django
    from pytest_django.fixtures import SettingsWrapper
    
    
    @pytest.fixture(scope='session')
    #@pytest.yield_fixture()
    def settings():
        """A Django settings object which restores changes after the testrun"""
        skip_if_no_django()
    
        wrapper = SettingsWrapper()
        yield wrapper
        wrapper.finalize()
    
    
    @pytest.fixture(autouse=True, scope='session')
    def media_root(settings):
        tmp_dir = tempfile.mkdtemp()
        settings.MEDIA_ROOT = tmp_dir
        yield settings.MEDIA_ROOT
        shutil.rmtree(tmp_dir)
    
    
    @pytest.fixture(scope='session')
    def django_db_setup(media_root, django_db_setup):
        print('inject_after')
    

    可能会有所帮助:

    1. https://dev.funkwhale.audio/funkwhale/funkwhale/blob/de777764da0c0e9fe66d0bb76317679be964588b/api/tests/conftest.py
    2. https://framagit.org/ideascube/ideascube/blob/master/conftest.py
    3. https://stackoverflow.com/a/56177770/5305401

    【讨论】:

      【解决方案5】:

      这就是我为测试所做的。上传文件后,它应该会出现在我的组织模型对象的 photo 属性中:

          import tempfile
          filename = tempfile.mkstemp()[1]
          f = open(filename, 'w')
          f.write('These are the file contents')
          f.close()
          f = open(filename, 'r')
          post_data = {'file': f}
          response = self.client.post("/org/%d/photo" % new_org_data["id"], post_data)
          f.close()
          self.assertEqual(response.status_code, 200)
      
          ## Check the file
          ## org is where the file should end up
          org = models.Organization.objects.get(pk=new_org_data["id"])
          self.assertEqual("These are the file contents", org.photo.file.read())
      
          ## Remove the file
          import os
          os.remove(org.photo.path)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-25
        • 2019-11-11
        • 1970-01-01
        • 2017-01-21
        • 2018-02-07
        • 2011-01-12
        • 2011-04-16
        • 2011-01-29
        相关资源
        最近更新 更多