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张文件;且图片字段的实际存储文件名与测试断言的预期不符,导致断言失败。
问题原因
- TestCase的setUp执行逻辑:Django的
TestCase会为每个test_*方法单独执行setUp初始化,你有3个测试方法,因此会创建3次Article实例,每次都会触发一次图片上传操作。 - 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
相关产品推荐
相关产品推荐

