cách tải lên đơn vị tệp thử nghiệm trong django


99

Trong ứng dụng django của tôi, tôi có một chế độ xem hoàn thành việc tải tệp lên. Đoạn mã cốt lõi như thế này

...
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)

Tôi muốn kiểm tra đơn vị chế độ xem.Tôi đang lên kế hoạch kiểm tra đường dẫn hạnh phúc cũng như đường dẫn thất bại..ie, trường hợp request.FILESkhông có khóa 'tệp', trường hợp request.FILES['file']None..

Làm cách nào để thiết lập dữ liệu bài đăng cho con đường hạnh phúc? Ai đó có thể cho tôi biết được không?


khi bạn đánh dấu câu trả lời bằng cách sử dụng lớp khách hàng là đúng, có thể bạn không tìm kiếm một bài kiểm tra đơn vị, mà là một bài kiểm tra chức năng ...
Henning

Câu trả lời:


109

Từ tài liệu Django trên Client.post:

Gửi hồ sơ là một trường hợp đặc biệt. Để ĐĂNG một tệp, bạn chỉ cần cung cấp tên trường tệp làm khóa và xử lý tệp cho tệp bạn muốn tải lên dưới dạng giá trị. Ví dụ:

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

12
liên kết đến tài liệu Django có liên quan: docs.djangoproject.com/en/dev/topics/testing/overview/…
lsh


2
Henning đúng về mặt kỹ thuật - điều này sẽ đúng hơn integration test- không thực sự quan trọng cho đến khi bạn đi vào các cơ sở mã phức tạp hơn, thậm chí có thể với một nhóm thử nghiệm thực tế
Alvin

Trong khuôn khổ web, nó ít tạo ra sự khác biệt hơn nếu bạn đang thử nghiệm các lượt xem. Nhận phản hồi thông qua máy khách so với trực tiếp từ chức năng là tương tự đủ để hầu hết các thử nghiệm có hiệu lực. Thêm vào đó, khách hàng cung cấp cho bạn sự linh hoạt hơn. Đó là những gì tôi sử dụng, cá nhân.
trpt4him

cập nhật liên kết đến tài liệu Django có liên quan: docs.djangoproject.com/en/dev/topics/testing/tools/…
đóng băng

109

Tôi đã từng làm như vậy with open('some_file.txt') as fp:nhưng sau đó tôi cần hình ảnh, video và các tệp thực khác trong repo và tôi cũng đang thử nghiệm một phần của thành phần lõi Django đã được kiểm tra tốt, vì vậy hiện tại đây là những gì tôi đang làm:

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 ...

Trong Python 3.5+, bạn cần sử dụng bytesđối tượng thay vì str. Thay đổi "file_content"thànhb"file_content"

Nó hoạt động tốt, SimpleUploadedFiletạo ra một InMemoryFilehoạt động giống như một tải lên thông thường và bạn có thể chọn tên, nội dung và loại nội dung.


1
Sử dụng ví dụ của bạn, xác thực biểu mẫu cung cấp cho tôi: "Tải lên hình ảnh hợp lệ. Tệp bạn đã tải lên không phải là hình ảnh hoặc hình ảnh bị hỏng."
antonagestam

@antonagestam Bạn có đang chuyển đúng loại nội dung không? Biểu mẫu của bạn có xác thực nội dung của tệp không? nếu vậy "file_content"cần phải là tiêu đề hình ảnh hợp lệ để mã của bạn nghĩ rằng đó là hình ảnh hợp lệ.
Danilo Cabello

Tiêu đề thích hợp cho JPEG và PNG là gì?
antonagestam

2
Đây nên được coi là câu trả lời chính xác cho vấn đề này. Cảm ơn @DaniloCabello.
mannysz

1
Bạn có thể sử dụng base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXL0Y4OHwAAErkJR tạo nội dung hình ảnh thực.
Howdedo

6

Tôi khuyên bạn nên xem qua Django RequestFactory . Đó là cách tốt nhất để mô phỏng dữ liệu được cung cấp trong yêu cầu.

Nói rằng, tôi đã tìm thấy một số sai sót trong mã của bạn.

  • thử nghiệm "đơn vị" có nghĩa là chỉ kiểm tra một "đơn vị" chức năng. Vì vậy, nếu bạn muốn kiểm tra chế độ xem đó, bạn sẽ kiểm tra chế độ xem và hệ thống tệp, ergo, không thực sự kiểm tra đơn vị. Để làm rõ hơn điểm này. Nếu bạn chạy thử nghiệm đó và chế độ xem hoạt động tốt, nhưng bạn không có quyền lưu tệp đó, thử nghiệm của bạn sẽ không thành công vì điều đó.
  • Điều quan trọng khác là tốc độ kiểm tra . Nếu bạn đang làm điều gì đó như TDD, tốc độ thực hiện các bài kiểm tra của bạn thực sự quan trọng. Truy cập vào bất kỳ I / O nào không phải là một ý kiến ​​hay .

Vì vậy, tôi khuyên bạn nên cấu trúc lại chế độ xem của mình để sử dụng một chức năng như:

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

Và làm một số chế nhạo về điều đó. Bạn có thể sử dụng Python Mock .

Tái bút: Bạn cũng có thể sử dụng Django Test Client Nhưng điều đó có nghĩa là bạn đang thêm một thứ nữa để kiểm tra, vì ứng dụng đó sử dụng Sessions, phần mềm trung gian, v.v. Không có gì tương tự như Unit Testing.


1
Tôi có thể sai, nhưng có vẻ như anh ấy muốn kiểm tra tích hợp và chỉ sử dụng thuật ngữ 'đơn vị kiểm tra' không chính xác.
jooks

1
@santiagobasulto Tôi là người mới trong TDD và tôi muốn tăng tốc độ thử nghiệm đơn vị của mình. Nhưng tôi có một số chế độ xem liên quan đến tải lên tệp cũng tải tệp lên bộ lưu trữ từ xa (Amazon S3) trong quá trình thử nghiệm đơn vị. An mà cần thời gian. Bạn có thể vui lòng mở rộng câu trả lời của mình để chỉ ra chi tiết cách tránh truy cập I / O trong khi thử nghiệm không?
Dmitry Wojciechowski

5
Xin chào @Dmitry. Mock là cách để đi đến đó. Bất cứ khi nào bạn phải truy cập vào một tài nguyên bên ngoài, bạn nên mô phỏng nó. Giả sử bạn có một dạng xem được gọi là profile_picturesử dụng nội bộ một upload_profile_picturehàm. Nếu bạn muốn kiểm tra chế độ xem đó, chỉ cần giả lập hàm bên trong và đảm bảo rằng nó được gọi trong bài kiểm tra của bạn. Đây là một ví dụ đơn giản: gist.github.com/santiagobasulto/6437356
santiagobasulto

4

Tôi làm điều gì đó như thế này cho ứng dụng liên quan đến sự kiện của riêng tôi nhưng bạn nên có nhiều mã hơn đủ để tiếp tục với trường hợp sử dụng của riêng mình

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

Tôi đã làm một cái gì đó như thế:

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')

Hàm create_image sẽ tạo hình ảnh nên bạn không cần cung cấp đường dẫn tĩnh của hình ảnh.

Lưu ý: Bạn có thể cập nhật mã theo mã của bạn. Mã này cho Python 3.6.


1

Trong Django 1.7, có thể giải quyết vấn đề với TestCase wich bằng cách sử dụng open (filepath, 'rb') nhưng khi sử dụng máy khách thử nghiệm, chúng tôi không kiểm soát được nó. Tôi nghĩ có lẽ tốt nhất nên đảm bảo rằng file.read () luôn trả về byte.

source: https://code.djangoproject.com/ticket/23912 , bởi KevinEtienne

Nếu không có tùy chọn rb, một TypeError sẽ xuất hiện:

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

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)

Câu trả lời duy nhất sử dụng APIRequestFactory
majkelx

0

Như đã đề cập trong tài liệu chính thức của Django :

Gửi hồ sơ là một trường hợp đặc biệt. Để ĐĂNG một tệp, bạn chỉ cần cung cấp tên trường tệp làm khóa và xử lý tệp cho tệp bạn muốn tải lên dưới dạng giá trị. Ví dụ:

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

Thông tin thêm: Làm thế nào để kiểm tra xem tệp có được truyền làm đối số cho một số hàm không?

Trong khi thử nghiệm, đôi khi chúng tôi muốn đảm bảo rằng tệp được truyền dưới dạng đối số cho một số hàm.

ví dụ

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

Trong các thử nghiệm, hãy sử dụng mô hình của Python như thế này:

# 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']),
)

0
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})

Hi vọng điêu nay co ich.


0

Tôi đang sử dụng Python == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0

Tôi đã thử self.client.postnhưng có một Resolver404ngoại lệ.

Sau đây làm việc cho tôi:

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'}
    )
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.