122

在我的 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没有关键“文件”的情况,request.FILES['file']None..

如何设置快乐路径的帖子数据?有人可以告诉我吗?

4

14 回答 14

139

我以前也这样做,with open('some_file.txt') as fp:但后来我需要 repo 中的图像、视频和其他真实文件,而且我正在测试经过良好测试的 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+中,您需要使用bytesobject 而不是str. 更改"file_content"b"file_content"

它运行良好,SimpleUploadedFile创建了一个InMemoryFile行为类似于常规上传的内容,您可以选择名称、内容和内容类型。

于 2014-12-07T17:07:38.873 回答
133

来自 Django 文档Client.post

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

c = Client()
with open('wishlist.doc') as fp:
  c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
于 2012-06-23T14:55:00.680 回答
7

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

话虽如此,我在您的代码中发现了几个缺陷。

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

因此,我建议您重构视图以使用如下函数:

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

并对此进行一些嘲笑。您可以使用Python Mock

PS:您也可以使用 Django Test Client但这意味着您要添加更多东西来测试,因为该客户端使用会话、中间件等。与单元测试没有任何相似之处。

于 2012-06-23T15:00:27.277 回答
5

我为自己的事件相关应用程序做了类似的事情,但你应该有足够多的代码来处理你自己的用例

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)
于 2012-06-23T16:50:28.453 回答
5

我做了类似的事情:

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。

于 2018-02-02T08:46:46.967 回答
2

正如Django 的官方文档中提到的:

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

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 的 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']),
)
于 2019-11-05T09:32:55.440 回答
2

如果您想通过文件上传添加其他数据,请按照以下方法

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将作为文件接收,其他参数通常作为后参数接收。

于 2020-11-11T12:10:30.557 回答
1

在 Django 1.7 中,TestCase 存在一个问题,可以通过使用 open(filepath, 'rb') 来解决,但是在使用测试客户端时,我们无法控制它。我认为最好确保 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
于 2015-03-10T22:58:29.827 回答
1
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)
于 2019-07-11T10:30:34.833 回答
1
from django.test import Client
from requests import Response

client = Client()
with open(template_path, 'rb') as f:
    file = SimpleUploadedFile('Name of the django file', f.read())
    response: Response = client.post(url, format='multipart', data={'file': file})

希望这可以帮助。

于 2019-11-28T20:36:53.990 回答
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'}
    )
于 2020-06-15T01:28:03.663 回答
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")
于 2021-08-11T09:56:40.813 回答
0

我正在使用 GraphQL,上传测试:

with open('test.jpg', 'rb') as fp:
    response = self.client.execute(query, variables, data={'image': [fp]})

类突变中的代码

@classmethod
def mutate(cls, root, info, **kwargs):
    if image := info.context.FILES.get("image", None):
        kwargs["image"] = image
    TestingMainModel.objects.get_or_create(
        id=kwargs["id"], 
        defaults=kwargs
    )
于 2021-09-12T11:22:51.843 回答
0

非常方便的模拟解决方案

from django.test import TestCase, override_settings
#use your own client request factory
from my_framework.test import APIClient

from django.core.files import File
import tempfile
from pathlib import Path
import mock

image_mock = mock.MagicMock(spec=File)
image_mock.name = 'image.png' # or smt else

class MyTest(TestCase):

    # I assume we want to put this file in storage
    # so to avoid putting garbage in our MEDIA_ROOT 
    # we're using temporary storage for test purposes
    @override_settings(MEDIA_ROOT=Path(tempfile.gettempdir()))
    def test_send_file(self):
        client = APIClient()
        client.post(
            '/endpoint/'
            {'file':image_mock},
            format="multipart"
        ) 
于 2022-02-09T13:56:58.273 回答