cara mengunggah file uji unit di django


99

Di aplikasi django saya, saya memiliki tampilan yang menyelesaikan pengunggahan file. Potongan intinya seperti ini

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

Saya ingin menguji tampilan unit. Saya berencana untuk menguji jalur bahagia serta jalur gagal .. yaitu, kasus di mana request.FILEStidak memiliki 'file' kunci, kasus di mana request.FILES['file']memiliki None..

Bagaimana cara mengatur data kiriman untuk jalur bahagia? Adakah yang bisa memberi tahu saya?


saat Anda menandai jawaban menggunakan kelas klien sebagai benar, Anda mungkin tidak mencari tes unit, tetapi tes fungsional ...
Henning

Jawaban:


109

Dari dokumen Django di Client.post:

Mengirim file adalah kasus khusus. Untuk POST file, Anda hanya perlu memberikan nama kolom file sebagai kunci, dan pegangan file ke file yang ingin Anda upload sebagai nilai. Sebagai contoh:

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

12
tautan ke dokumen Django yang relevan: docs.djangoproject.com/en/dev/topics/testing/overview/…
lsh


2
Henning secara teknis benar - ini akan menjadi lebih dari integration test- tidak benar-benar masalah sampai Anda masuk ke basis kode yang lebih kompleks bahkan mungkin dengan tim penguji yang sebenarnya
Alvin

Dalam kerangka web, tidak ada bedanya jika Anda menguji tampilan. Mendapatkan respons melalui klien vs. langsung dari fungsinya cukup mirip agar sebagian besar pengujian valid. Ditambah klien memberi Anda lebih banyak fleksibilitas. Itulah yang saya gunakan, secara pribadi.
trpt4him

tautan pembaruan ke dokumen Django yang relevan: docs.djangoproject.com/en/dev/topics/testing/tools/…
dibekukan

109

Saya biasa melakukan hal yang sama with open('some_file.txt') as fp:tetapi kemudian saya membutuhkan gambar, video dan file nyata lainnya di repo dan juga saya sedang menguji bagian dari komponen inti Django yang telah diuji dengan baik, jadi saat ini inilah yang telah saya lakukan:

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

Di Python 3.5+ Anda perlu menggunakan bytesobjek, bukan str. Ganti "file_content"keb"file_content"

Ini berfungsi dengan baik, SimpleUploadedFilemembuat InMemoryFileyang berperilaku seperti upload biasa dan Anda dapat memilih nama, konten dan jenis konten.


1
Menggunakan contoh Anda, validasi formulir memberi saya: "Unggah gambar yang valid. File yang Anda unggah bukan gambar atau gambar rusak."
antonagestam

@antonagestam Apakah Anda menyampaikan jenis konten yang benar? Apakah formulir Anda memvalidasi konten file? jika demikian "file_content"harus berupa header gambar yang valid sehingga kode Anda menganggapnya sebagai gambar yang valid.
Danilo Cabello

Apa tajuk yang sesuai untuk JPEG dan PNG?
antonagestam

2
Ini harus dianggap sebagai jawaban yang benar untuk masalah ini. Terima kasih @DaniloCabello.
mannysz

1
Anda dapat menggunakan base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXL0Y4ggwAAE=ABJR ini membuat konten gambar nyata.
Howdedo

6

Saya merekomendasikan Anda untuk melihat Django RequestFactory . Ini adalah cara terbaik untuk memalsukan data yang diberikan dalam permintaan.

Mengatakan bahwa, saya menemukan beberapa kekurangan dalam kode Anda.

  • Pengujian "unit" berarti menguji hanya satu "unit" fungsionalitas. Jadi, jika Anda ingin menguji tampilan tersebut, Anda akan menguji tampilan tersebut, dan sistem file, ergo, bukan pengujian unit sebenarnya. Untuk memperjelas poin ini. Jika Anda menjalankan pengujian itu, dan tampilan berfungsi dengan baik, tetapi Anda tidak memiliki izin untuk menyimpan file tersebut, pengujian Anda akan gagal karenanya.
  • Hal penting lainnya adalah kecepatan uji . Jika Anda melakukan sesuatu seperti TDD, kecepatan eksekusi pengujian Anda sangat penting. Mengakses I / O apa pun bukanlah ide yang bagus .

Jadi, saya menyarankan Anda untuk memfaktorkan ulang pandangan Anda untuk menggunakan fungsi seperti:

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

Dan lakukan beberapa ejekan tentang itu. Anda dapat menggunakan Python Mock .

PS: Anda juga dapat menggunakan Django Test Client Tetapi itu berarti bahwa Anda menambahkan hal lain lagi untuk diuji, karena klien tersebut menggunakan Sesi, middlewares, dll. Tidak ada yang mirip dengan Pengujian Unit.


1
Saya bisa saja salah, tetapi sepertinya dia bermaksud menguji integrasi dan hanya menggunakan istilah 'uji unit' secara tidak benar.
jooks

1
@santiagobasulto Saya seorang pemula di TDD dan saya ingin mempercepat pengujian unit saya. Tetapi saya memiliki beberapa pandangan yang berhubungan dengan unggahan file yang mengunggah file ke penyimpanan jarak jauh (Amazon S3) selama pengujian unit juga. Itu membutuhkan waktu. Bisakah Anda memperluas jawaban Anda untuk menunjukkan secara rinci bagaimana menghindari akses I / O saat pengujian?
Dmitry Wojciechowski

5
Hei @Di. Mock adalah cara untuk pergi ke sana. Setiap kali Anda harus mengakses sumber daya eksternal, Anda harus mengejeknya. Misalkan Anda memiliki tampilan yang dipanggil profile_pictureyang secara internal menggunakan upload_profile_picturefungsi. Jika Anda ingin menguji tampilan tersebut, cukup tiru fungsi internal dan pastikan itu dipanggil pada pengujian Anda. Ini adalah contoh sederhana: gist.github.com/santiagobasulto/6437356
santiagobasulto

4

Saya melakukan sesuatu seperti ini untuk aplikasi terkait acara saya sendiri, tetapi Anda harus memiliki lebih dari cukup kode untuk melanjutkan kasus penggunaan Anda sendiri

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

Saya melakukan sesuatu seperti itu:

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

Fungsi create_image akan membuat gambar sehingga Anda tidak perlu memberikan jalur statis gambar.

Catatan: Anda dapat memperbarui kode sesuai kode Anda. Kode ini untuk Python 3.6.


1

Dalam Django 1.7 ada masalah dengan TestCase yang dapat diselesaikan dengan menggunakan open (filepath, 'rb') tetapi ketika menggunakan klien percobaan kami tidak memiliki kendali atasnya. Saya pikir mungkin yang terbaik adalah memastikan file.read () selalu mengembalikan byte.

sumber: https://code.djangoproject.com/ticket/23912 , oleh KevinEtienne

Tanpa opsi rb, TypeError dimunculkan:

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)

Satu-satunya jawaban menggunakan APIRequestFactory
majkelx

0

Seperti disebutkan dalam dokumentasi resmi Django :

Mengirim file adalah kasus khusus. Untuk POST file, Anda hanya perlu memberikan nama kolom file sebagai kunci, dan pegangan file ke file yang ingin Anda upload sebagai nilai. Sebagai contoh:

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

Informasi Lebih Lanjut: Bagaimana cara memeriksa apakah file dilewatkan sebagai argumen ke beberapa fungsi?

Saat menguji, terkadang kami ingin memastikan bahwa file tersebut diteruskan sebagai argumen ke beberapa fungsi.

misalnya

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

Dalam pengujian, gunakan tiruan Python seperti ini:

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

Semoga ini membantu.


0

Saya menggunakan Python == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0

Saya mencoba self.client.posttetapi mendapat Resolver404pengecualian.

Berikut ini berhasil untuk saya:

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'}
    )
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.