วิธีการอัปโหลดไฟล์ทดสอบหน่วยใน django


102

ในแอป 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)

ฉันต้องการทดสอบหน่วยการดูฉันกำลังวางแผนที่จะทดสอบเส้นทางที่มีความสุขเช่นเดียวกับเส้นทางที่ล้มเหลว ..ie กรณีที่request.FILESไม่มีคีย์ 'ไฟล์' กรณีที่request.FILES['file']มีNone..

จะตั้งค่าข้อมูลโพสต์เส้นทางความสุขได้อย่างไรใครช่วยบอกที


ในขณะที่คุณทำเครื่องหมายคำตอบโดยใช้คลาสไคลเอนต์ให้ถูกต้องคุณอาจไม่ได้มองหาการทดสอบหน่วย แต่เป็นการทดสอบการทำงาน ...
เฮนนิง

คำตอบ:


113

จาก Django docs บนClient.post:

การส่งไฟล์เป็นกรณีพิเศษ ในการโพสต์ไฟล์คุณต้องระบุเพียงชื่อฟิลด์ไฟล์เป็นคีย์และหมายเลขอ้างอิงของไฟล์ที่คุณต้องการอัปโหลดเป็นค่า ตัวอย่างเช่น:

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

12
ลิงก์ไปยังเอกสาร Django ที่เกี่ยวข้อง: docs.djangoproject.com/en/dev/topics/testing/overview/…
lsh


2
Henning ถูกต้องในทางเทคนิค - นี่จะเป็นมากกว่าintegration test- ไม่สำคัญจริง ๆ จนกว่าคุณจะเข้าสู่ฐานรหัสที่ซับซ้อนมากขึ้นแม้กระทั่งกับทีมทดสอบจริง
Alvin

ในเว็บเฟรมเวิร์กจะสร้างความแตกต่างน้อยกว่ามากหากคุณกำลังทดสอบมุมมอง การตอบสนองผ่านไคลเอนต์และโดยตรงจากฟังก์ชันนั้นคล้ายคลึงกันมากพอที่การทดสอบส่วนใหญ่จะใช้ได้ นอกจากนี้ลูกค้ายังช่วยให้คุณมีความยืดหยุ่นมากขึ้น นั่นคือสิ่งที่ฉันใช้เป็นการส่วนตัว
trpt4him

1
การปรับปรุงการเชื่อมโยงไปยังเอกสาร Django ที่เกี่ยวข้อง: docs.djangoproject.com/en/dev/topics/testing/tools/...
แช่แข็ง

117

ฉันเคยทำแบบเดียวกัน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ที่ทำงานเหมือนการอัปโหลดทั่วไปและคุณสามารถเลือกชื่อเนื้อหาและประเภทเนื้อหาได้


1
การใช้ตัวอย่างของคุณการตรวจสอบแบบฟอร์มให้ฉัน: "อัปโหลดรูปภาพที่ถูกต้องไฟล์ที่คุณอัปโหลดไม่ใช่รูปภาพหรือรูปภาพที่เสียหาย"
antonagestam

@antonagestam คุณส่งเนื้อหาที่ถูกต้องหรือไม่ แบบฟอร์มของคุณกำลังตรวจสอบเนื้อหาของไฟล์หรือไม่ หาก"file_content"จำเป็นต้องเป็นส่วนหัวของรูปภาพที่ถูกต้องเพื่อให้โค้ดของคุณคิดว่าเป็นรูปภาพที่ถูกต้อง
Danilo Cabello

ส่วนหัวที่เหมาะสมสำหรับ JPEG และ PNG คืออะไร?
antonagestam

2
นี่ควรถือเป็นคำตอบที่ถูกต้องสำหรับปัญหานี้ ขอบคุณ @DaniloCabello
mannysz

2
คุณสามารถใช้ base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXL0Y4OHwAAAABRgg" เนื้อหานี้เป็นรูปภาพจริง
Howdedo

6

ผมขอแนะนำให้คุณที่จะดูที่ Django RequestFactory เป็นวิธีที่ดีที่สุดในการจำลองข้อมูลที่ให้ไว้ในคำขอ

บอกว่าฉันพบข้อบกพร่องหลายประการในรหัสของคุณ

  • การทดสอบ "หน่วย" หมายถึงการทดสอบฟังก์ชันการทำงานเพียง "หน่วย" เดียว ดังนั้นหากคุณต้องการทดสอบมุมมองนั้นคุณกำลังทดสอบมุมมองและระบบไฟล์ ergo ไม่ใช่การทดสอบหน่วยจริงๆ เพื่อให้ประเด็นนี้ชัดเจนยิ่งขึ้น. หากคุณเรียกใช้การทดสอบนั้นและมุมมองทำงานได้ดี แต่คุณไม่มีสิทธิ์ในการบันทึกไฟล์นั้นการทดสอบของคุณจะล้มเหลวเพราะเหตุนี้
  • สิ่งที่สำคัญอื่น ๆ คือความเร็วในการทดสอบ หากคุณกำลังทำอะไรบางอย่างเช่น TDD ความเร็วในการดำเนินการทดสอบของคุณเป็นสิ่งสำคัญมาก การเข้าถึงใด ๆ ฉัน / O ไม่ได้เป็นความคิดที่ดี

ดังนั้นผมแนะนำให้คุณrefactorมุมมองของคุณที่จะใช้ฟังก์ชั่นที่ชอบ:

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

และเยาะเย้ยเรื่องนั้น คุณสามารถใช้งูหลามจำลอง

PS: คุณสามารถใช้ Django Test Client ได้เช่นกันแต่นั่นหมายความว่าคุณกำลังเพิ่มสิ่งอื่นในการทดสอบเนื่องจากไคลเอนต์นั้นใช้ประโยชน์จากเซสชันตัวกลางและอื่น ๆ ไม่มีอะไรที่คล้ายกับการทดสอบหน่วย


1
ฉันอาจจะผิด แต่ดูเหมือนว่าเขาตั้งใจจะทดสอบการบูรณาการและใช้คำว่า 'unit test' ไม่ถูกต้อง
jooks

1
@santiagobasulto ฉันเป็นมือใหม่ใน TDD และฉันต้องการเร่งการทดสอบหน่วยของฉัน แต่ฉันมีหลายมุมมองเกี่ยวกับการอัปโหลดไฟล์ซึ่งอัปโหลดไฟล์ไปยังที่เก็บข้อมูลระยะไกล (Amazon S3) ระหว่างการทดสอบหน่วยเช่นกัน ที่ต้องใช้เวลา โปรดขยายคำตอบเพื่อแสดงรายละเอียดวิธีหลีกเลี่ยงการเข้าถึง I / O ขณะทดสอบได้หรือไม่
Dmitry Wojciechowski

6
เฮ้ @Dmitry จำลองเป็นวิธีที่จะไปที่นั่น เมื่อใดก็ตามที่คุณต้องเข้าถึงทรัพยากรภายนอกคุณควรเยาะเย้ยมัน สมมติว่าคุณมีมุมมองที่เรียกprofile_pictureว่าใช้upload_profile_pictureฟังก์ชันภายใน หากคุณต้องการทดสอบมุมมองนั้นเพียงแค่จำลองฟังก์ชันภายในและตรวจสอบให้แน่ใจว่ามีการเรียกใช้การทดสอบของคุณ นี่เป็นตัวอย่างง่ายๆ: gist.github.com/santiagobasulto/6437356
santiagobasulto

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

ฉันทำอะไรแบบนั้น:

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


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

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)

คำตอบเดียวที่ใช้ APIRequestFactory
majkelx

1

ดังที่กล่าวไว้ในเอกสารอย่างเป็นทางการของ 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 '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})

หวังว่านี่จะช่วยได้


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'}
    )
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.