如何在django中单元测试文件上传

时间:2012-06-23 14:49:08

标签: django unit-testing file-upload

在我的django应用程序中,我有一个可以完成文件上传的视图。核心代码段是这样的

...
if  (request.method == 'POST'):
    if request.FILES.has_key('file'):
        file = request.FILES['file']
        with open(settings.destfolder+'/%s' % file.name, 'wb+') as dest:
            for chunk in file.chunks():
                dest.write(chunk)

我想对视图进行单元测试。我打算测试快乐路径以及失败路径..在request.FILES没有密钥'文件'的情况下,{{ 1}}有request.FILES['file'] ..

如何为快乐路径设置发布数据?有人可以告诉我吗?

12 个答案:

答案 0 :(得分:97)

来自Client.post的Django文档:

  

提交文件是一种特殊情况。要发布文件,只需要   提供文件字段名称作为键,以及文件的文件句柄   您希望上传为值。例如:

c = Client()
with open('wishlist.doc') as fp:
  c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})

答案 1 :(得分:79)

我曾经做同样的with open('some_file.txt') as fp:,但后来我需要回购中的图像,视频和其他真实文件,而且我正在测试经过充分测试的Django核心组件的一部分,所以目前这是什么我一直在做:

from django.core.files.uploadedfile import SimpleUploadedFile

def test_upload_video(self):
    video = SimpleUploadedFile("file.mp4", "file_content", content_type="video/mp4")
    self.client.post(reverse('app:some_view'), {'video': video})
    # some important assertions ...

Python 3.5 + 中,您需要使用bytes对象而不是str。将"file_content"更改为b"file_content"

一切正常,SimpleUploadedFile创建的InMemoryFile行为与常规上传相似,您可以选择名称,内容和内容类型。

答案 2 :(得分:6)

我建议你看一下Django RequestFactory。这是模拟请求中提供的数据的最佳方式。

说,我在你的代码中发现了一些缺陷。

  • “unit”测试意味着测试功能性的一个“单位”。所以, 如果你想测试那个视图,你将测试视图和文件 系统,ergo,不是真正的单元测试。为了更清楚地说明这一点。如果 你运行该测试,视图工作正常,但你没有 保存该文件的权限,您的测试将因此而失败。
  • 其他重要的是测试速度。如果你正在做类似的事情 TDD执行测试的速度非常重要。 访问任何I / O不是一个好主意

因此,我建议您重构您的视图以使用以下功能:

def upload_file_to_location(request, location=None): # Can use the default configured

并对此做一些嘲弄。您可以使用Python Mock

PS:你也可以使用Django Test Client但这意味着你要添加更多的东西来测试,因为客户端使用Sessions,middleweres等。没有类似于单元测试。

答案 3 :(得分:4)

我为自己的事件相关应用程序执行类似的操作,但是您应该有足够的代码来继续使用自己的用例

import tempfile, csv, os

class UploadPaperTest(TestCase):

    def generate_file(self):
        try:
            myfile = open('test.csv', 'wb')
            wr = csv.writer(myfile)
            wr.writerow(('Paper ID','Paper Title', 'Authors'))
            wr.writerow(('1','Title1', 'Author1'))
            wr.writerow(('2','Title2', 'Author2'))
            wr.writerow(('3','Title3', 'Author3'))
        finally:
            myfile.close()

        return myfile

    def setUp(self):
        self.user = create_fuser()
        self.profile = ProfileFactory(user=self.user)
        self.event = EventFactory()
        self.client = Client()
        self.module = ModuleFactory()
        self.event_module = EventModule.objects.get_or_create(event=self.event,
                module=self.module)[0]
        add_to_admin(self.event, self.user)

    def test_paper_upload(self):
        response = self.client.login(username=self.user.email, password='foz')
        self.assertTrue(response)

        myfile = self.generate_file()
        file_path = myfile.name
        f = open(file_path, "r")

        url = reverse('registration_upload_papers', args=[self.event.slug])

        # post wrong data type
        post_data = {'uploaded_file': i}
        response = self.client.post(url, post_data)
        self.assertContains(response, 'File type is not supported.')

        post_data['uploaded_file'] = f
        response = self.client.post(url, post_data)

        import_file = SubmissionImportFile.objects.all()[0]
        self.assertEqual(SubmissionImportFile.objects.all().count(), 1)
        #self.assertEqual(import_file.uploaded_file.name, 'files/registration/{0}'.format(file_path))

        os.remove(myfile.name)
        file_path = import_file.uploaded_file.path
        os.remove(file_path)

答案 4 :(得分:1)

在Django 1.7中,使用open(filepath,'rb')可以解决TestCase的问题,但是在使用测试客户端时,我们无法控制它。我认为最好确保file.read()总是返回字节。

来源:https://code.djangoproject.com/ticket/23912,作者:KevinEtienne

如果没有rb选项,则会引发TypeError:

TypeError: sequence item 4: expected bytes, bytearray, or an object with the buffer interface, str found

答案 5 :(得分:1)

我做了类似的事情:

from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.core.files import File
from django.utils.six import BytesIO

from .forms import UploadImageForm

from PIL import Image
from io import StringIO


def create_image(storage, filename, size=(100, 100), image_mode='RGB', image_format='PNG'):
   """
   Generate a test image, returning the filename that it was saved as.

   If ``storage`` is ``None``, the BytesIO containing the image data
   will be passed instead.
   """
   data = BytesIO()
   Image.new(image_mode, size).save(data, image_format)
   data.seek(0)
   if not storage:
       return data
   image_file = ContentFile(data.read())
   return storage.save(filename, image_file)


class UploadImageTests(TestCase):
   def setUp(self):
       super(UploadImageTests, self).setUp()


   def test_valid_form(self):
       '''
       valid post data should redirect
       The expected behavior is to show the image
       '''
       url = reverse('image')
       avatar = create_image(None, 'avatar.png')
       avatar_file = SimpleUploadedFile('front.png', avatar.getvalue())
       data = {'image': avatar_file}
       response = self.client.post(url, data, follow=True)
       image_src = response.context.get('image_src')

       self.assertEquals(response.status_code, 200)
       self.assertTrue(image_src)
       self.assertTemplateUsed('content_upload/result_image.html')

create_image函数将创建图像,因此您不需要提供图像的静态路径。

注意:您可以根据代码更新代码。 这段代码适用于Python 3.6。

答案 6 :(得分:0)

from rest_framework.test import force_authenticate
from rest_framework.test import APIRequestFactory

factory = APIRequestFactory()
user = User.objects.get(username='#####')
view = <your_view_name>.as_view()
with open('<file_name>.pdf', 'rb') as fp:
    request=factory.post('<url_path>',{'file_name':fp})
force_authenticate(request, user)
response = view(request)

答案 7 :(得分:0)

Django's official documentation中所述:

  

提交文件是一种特殊情况。要发布文件,只需提供文件字段名称作为键,并提供要上传的文件的文件句柄作为值。例如:

c = Client()
with open('wishlist.doc') as fp:
    c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})

更多信息:如何检查文件是否作为参数传递给某些函数?

在测试时,有时我们要确保将文件作为参数传递给某些函数。

例如

...
class AnyView(CreateView):
    ...
    def post(self, request, *args, **kwargs):
        attachment = request.FILES['attachment']
        # pass the file as an argument
        my_function(attachment)
        ...

在测试中,请使用Python's mock这样的内容:

# Mock 'my_function' and then check the following:

response = do_a_post_request()

self.assertEqual(mock_my_function.call_count, 1)
self.assertEqual(
    mock_my_function.call_args,
    call(response.wsgi_request.FILES['attachment']),
)

答案 8 :(得分:0)

cmake ..

希望这会有所帮助。

答案 9 :(得分:0)

我正在使用Python == 3.8.2,Django == 3.0.4,djangorestframework == 3.11.0

我尝试了self.client.post,但是遇到了Resolver404异常。

以下为我工作:

import requests
upload_url='www.some.com/oaisjdoasjd' # your url to upload
with open('/home/xyz/video1.webm', 'rb') as video_file:
    # if it was a text file we would perhaps do
    # file = video_file.read()
    response_upload = requests.put(
        upload_url,
        data=video_file,
        headers={'content-type': 'video/webm'}
    )

答案 10 :(得分:0)

如果要通过文件上传添加其他数据,请遵循以下方法

file = open('path/to/file.txt', 'r', encoding='utf-8')

    data = {
        'file_name_to_receive_on_backend': file,
        'param1': 1,
        'param2': 2,
        .
        .
    }

    response = self.client.post("/url/to/view", data, format='multipart')`

唯一的file_name_to_receive_on_backend将作为文件接收,其他参数通常作为后置paramas接收。

答案 11 :(得分:0)

我正在使用 django rest 框架,我不得不测试多个文件的上传。

我终于通过在我的 format="multipart" 请求中使用 APIClient.post 获得了它。

from rest_framework.test import APIClient
...
    self.client = APIClient()
    with open('./photo.jpg', 'rb') as fp:
        resp = self.client.post('/upload/',
                                {'images': [fp]},
                                format="multipart")