django'da dosya yüklemesi nasıl birim test edilir


99

Django uygulamamda, dosya yüklemeyi gerçekleştiren bir görünüme sahibim. Çekirdek snippet şuna benzer

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

Görünümü birim test etmek istiyorum. Mutlu yolu ve başarısız yolu test etmeyi planlıyorum..ie, request.FILESanahtarın olmadığı durumda 'dosya', nerede request.FILES['file']olduğu None...

Mutlu yol için gönderi verilerini nasıl kurarım? Biri bana söyleyebilir mi?


Müşteri sınıfını kullanarak yanıtı doğru olarak işaretlediğinizde, muhtemelen bir birim testi değil, işlevsel bir test arıyorsunuz ...
Henning

Yanıtlar:


109

Django belgelerinden Client.post:

Dosya göndermek özel bir durumdur. Bir dosyayı POST yapmak için, anahtar olarak yalnızca dosya alanı adını ve bir değer olarak yüklemek istediğiniz dosyaya bir dosya tanıtıcısı sağlamanız gerekir. Örneğin:

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

12
İlgili Django Dokümanın bağlantısı: docs.djangoproject.com/en/dev/topics/testing/overview/...
LSH


2
Henning teknik olarak doğru - bu daha çok bir şey olurdu integration test- aslında daha karmaşık kod tabanlarına girene kadar önemli değil, belki gerçek bir test ekibiyle bile
Alvin

Bir web çerçevesinde, görünümleri test etmeniz çok daha az fark yaratır. Yanıtın istemci aracılığıyla veya doğrudan işlevden alınması çoğu testin geçerli olması için yeterince benzerdir. Ayrıca müşteri size daha fazla esneklik sağlar. Kişisel olarak kullandığım şey bu.
trpt4him

İlgili Django belgesine
donduruldu

111

Eskiden aynısını yapardım with open('some_file.txt') as fp:ama sonra depoda resimlere, videolara ve diğer gerçek dosyalara ihtiyacım vardı ve ayrıca iyi test edilmiş bir Django çekirdek bileşeninin bir parçasını test ediyordum, bu yüzden şu anda yaptığım şey buydu:

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

In Python 3.5+ kullanmak gerekir bytesyerine nesne str. Değişim "file_content"içinb"file_content"

İyi çalışıyor, normal bir yükleme gibi davranan SimpleUploadedFilebir InMemoryFileşey yaratıyor ve adı, içeriği ve içerik türünü seçebilirsiniz.


1
Örneğinizi kullanarak, form doğrulama bana şunu verir: "Geçerli bir resim yükleyin. Yüklediğiniz dosya bir resim değil veya bozuk bir resimdi."
antonagestam

@antonagestam Doğru içerik türünü iletiyor musunuz? Formunuz dosyanın içeriğini doğruluyor mu? eğer öyleyse "file_content"geçerli bir resim başlığı olması gerekiyorsa, kodunuz bunun geçerli bir resim olduğunu düşünür.
Danilo Cabello

JPEG ve PNG için uygun başlıklar nelerdir?
antonagestam

2
Bu, bu sorunun doğru cevabı olarak düşünülmelidir. Teşekkürler @DaniloCabello.
mannysz

1
Base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxglggjNBA5" +OHxglggJNBA5 "+OHxglggjNBA5 içeriği olarak" 9TAXL0Y4AO "görüntü olarak) kullanabilirsiniz.
Howdedo

6

Django RequestFactory'ye bir göz atmanızı tavsiye ederim . Talepte sağlanan verilerle alay etmenin en iyi yolu budur.

Dedi ki, kodunuzda birkaç kusur buldum.

  • "birim" testi , işlevselliğin yalnızca bir "birimini" test etmek anlamına gelir . Yani, bu görünümü test etmek istiyorsanız, görünümü ve dosya sistemini test edersiniz, ergo, gerçekten birim testi değil. Bu noktayı daha açık hale getirmek için. Bu testi çalıştırırsanız ve görünüm iyi çalışırsa, ancak bu dosyayı kaydetme izniniz yoksa, bu nedenle testiniz başarısız olur.
  • Diğer önemli şey test hızıdır . TDD gibi bir şey yapıyorsanız, testlerinizin gerçekleştirilme hızı gerçekten önemlidir. Herhangi bir G / Ç'ye erişmek iyi bir fikir değildir .

Bu nedenle, aşağıdaki gibi bir işlevi kullanmak için görünümünüzü yeniden düzenlemenizi öneririm :

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

Ve bununla biraz alay et. Python Mock'u kullanabilirsiniz .

Not: Django Test İstemcisini de kullanabilirsiniz. Ancak bu, test etmek için başka bir şey eklemeniz anlamına gelir, çünkü bu müşteri Oturumları, ara yazılımları vb. Kullanır. Birim Testine benzer bir şey yoktur.


1
Yanılıyor olabilirim, ama görünüşe göre entegrasyon testi yapmak istiyordu ve sadece 'birim testi' terimini yanlış kullandı.
2013

1
@santiagobasulto TDD'de acemiyim ve birim testlerimi hızlandırmak istiyorum. Ancak, birim testi sırasında da uzak depolamaya (Amazon S3) dosya yükleyen dosya yüklemeleriyle ilgili birkaç görünümüm var. Zaman alan bir. Lütfen test sırasında I / O'ya erişimden nasıl kaçınılacağını ayrıntılı olarak göstermek için yanıtınızı genişletir misiniz?
Dmitry Wojciechowski

5
Hey @Dmitry. Mock, oraya gitmenin yoludur. Harici bir kaynağa erişmeniz gerektiğinde, onunla dalga geçmelisiniz. profile_pictureDahili olarak bir upload_profile_pictureişlevi kullanan bir görünümünüz olduğunu varsayalım . Bu görünümü test etmek istiyorsanız, dahili işlevle alay edin ve testinizde çağrıldığından emin olun. Bu basit bir örnek: gist.github.com/santiagobasulto/6437356
santiagobasulto

4

Olayla ilgili kendi uygulamam için buna benzer bir şey yapıyorum, ancak kendi kullanım durumunuza devam etmek için fazlasıyla yeterli koda sahip olmalısınız

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

Böyle bir şey yaptım:

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 fonksiyonu imaj yaratacaktır, böylece statik imaj yolu vermenize gerek kalmaz.

Not: Kodu kodunuza göre güncelleyebilirsiniz. Python 3.6 için bu kod.


1

Django 1.7'de TestCase ile open (filepath, 'rb') kullanılarak çözülebilen bir sorun var, ancak test istemcisini kullanırken onun üzerinde hiçbir kontrolümüz yok. Bence file.read () işlevinin her zaman bayt döndürmesini sağlamak en iyisidir.

kaynak: https://code.djangoproject.com/ticket/23912 , KevinEtienne

Rb seçeneği olmadan bir TypeError yükseltilir:

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'yi kullanan tek cevap
majkelx

0

Django'nun resmi belgelerinde belirtildiği gibi :

Dosya göndermek özel bir durumdur. Bir dosyayı POST yapmak için, anahtar olarak yalnızca dosya alanı adını ve bir değer olarak yüklemek istediğiniz dosyaya bir dosya tanıtıcısı sağlamanız gerekir. Örneğin:

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

Daha Fazla Bilgi: Dosyanın bir işleve argüman olarak aktarılıp aktarılmadığı nasıl kontrol edilir?

Test ederken bazen dosyanın bir fonksiyona argüman olarak aktarıldığından emin olmak isteriz.

Örneğin

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

Testlerde, Python'un taklidini şu şekilde kullanın:

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

Bu yardımcı olur umarım.


0

Python == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0 kullanıyorum

Denedim self.client.postama bir Resolver404istisna var .

Aşağıdakiler benim için çalıştı:

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'}
    )
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.