You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django单元测试:SimpleUploadedFile生成多张图片的原因及修复

Django Article模型单元测试:图片字段断言失败与多重复文件生成问题

测试代码

from django.test import TestCase
from django.utils import timezone
from django.core.files.uploadedfile import SimpleUploadedFile
from .models import Article

class ArticleModelTestCase(TestCase):
    def setUp(self):
        self.article = Article.objects.create(
            title = 'Title test.',
            content = 'Content test.',
            date_posted = timezone.localtime(),
            image = SimpleUploadedFile(
                name = 'test_image.jpg',
                content = b'',
                content_type = 'image/jpeg'
            )
        )

    def test_title(self):
        self.assertEqual(self.article.title, 'Title test.')

    def test_content(self):
        self.assertEqual(self.article.content, 'Content test.')

    def test_image_field(self):
        self.assertEqual(self.article.image.name, 'test_image.jpg')

错误输出

Creating test database for alias 'default'... System check identified no issues (0 silenced). .F.
======================================================================
FAIL: test_image_field (articles.tests.ArticleModelTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/xxx/projects/xxxx/backend/articles/tests.py", line 27, in test_image_field
    self.assertEqual(self.article.image.name, 'test_image.jpg')
AssertionError: 'article_images/test_image_7U9qlax.jpg' != 'test_image.jpg'
- article_images/test_image_7U9qlax.jpg
+ test_image.jpg

----------------------------------------------------------------------
Ran 3 tests in 0.004s

FAILED (failures=1)
Destroying test database for alias 'default'...

异常现象

每次运行测试会生成3张带随机后缀的图片文件(如test_image_CWK5SDf.jpg),加上初始的test_image.jpg,共4张文件;且图片字段的实际存储文件名与测试断言的预期不符,导致断言失败。


问题原因

  1. TestCase的setUp执行逻辑:Django的TestCase会为每个test_*方法单独执行setUp初始化,你有3个测试方法,因此会创建3次Article实例,每次都会触发一次图片上传操作。
  2. Django默认文件存储的冲突处理:Django的FileSystemStorage(FileField默认使用的存储类)会自动处理文件名冲突——当目标路径已存在同名文件时,会调用get_available_name生成带随机后缀的新文件名,防止覆盖已有文件。这就是为什么会生成多个带随机字符串的图片,且断言时实际文件名与预期不一致。

修复方案

方案1:调整断言逻辑(最简便)

无需修改测试实例的创建逻辑,只需要修改断言,验证文件名包含预期的核心标识即可:

def test_image_field(self):
    # 验证文件名包含预期的名称,且为jpg格式
    self.assertIn('test_image', self.article.image.name)
    self.assertTrue(self.article.image.name.endswith('.jpg'))

方案2:使用setUpClass实现单次实例创建

如果希望整个测试类只创建一次Article实例,避免重复上传图片,改用类级别的初始化方法setUpClass,并在测试结束后清理文件:

class ArticleModelTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.article = Article.objects.create(
            title='Title test.',
            content='Content test.',
            date_posted=timezone.localtime(),
            image=SimpleUploadedFile(
                name='test_image.jpg',
                content=b'',
                content_type='image/jpeg'
            )
        )

    @classmethod
    def tearDownClass(cls):
        # 手动删除测试生成的图片文件,避免残留
        cls.article.image.delete()
        super().tearDownClass()

    def test_title(self):
        self.assertEqual(self.article.title, 'Title test.')

    def test_content(self):
        self.assertEqual(self.article.content, 'Content test.')

    def test_image_field(self):
        # 注意需包含upload_to指定的路径前缀
        self.assertTrue(self.article.image.name.endswith('test_image.jpg'))

方案3:自定义存储类禁用文件名重命名(适合特殊测试场景)

如果必须保持原文件名不变,可以自定义存储类覆盖冲突处理逻辑,仅在测试环境使用:

from django.core.files.storage import FileSystemStorage

class NoRenameStorage(FileSystemStorage):
    def get_available_name(self, name, max_length=None):
        # 直接返回原文件名,不处理冲突(仅适用于测试)
        return name

class ArticleModelTestCase(TestCase):
    def setUp(self):
        # 临时替换图片字段的存储类
        self.original_storage = Article.image.field.storage
        Article.image.field.storage = NoRenameStorage()
        
        self.article = Article.objects.create(
            title='Title test.',
            content='Content test.',
            date_posted=timezone.localtime(),
            image=SimpleUploadedFile(
                name='test_image.jpg',
                content=b'',
                content_type='image/jpeg'
            )
        )

    def tearDown(self):
        # 恢复原存储类
        Article.image.field.storage = self.original_storage
        # 清理测试文件
        self.article.image.delete()

    def test_image_field(self):
        # 此时可断言完整文件名(需包含upload_to路径)
        self.assertEqual(self.article.image.name, 'article_images/test_image.jpg')

注意:该方案会导致文件名冲突时直接覆盖已有文件,仅适合测试环境使用,禁止在生产环境启用。


内容的提问来源于stack exchange,提问作者Sir Broccolia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:32:25