come caricare file di unit test in django


99

Nella mia app django, ho una vista che completa il caricamento del file. Lo snippet principale è così

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

Vorrei eseguire un test unitario della vista. Sto pianificando di testare il percorso felice e il percorso di errore .. ad esempio, il caso in cui request.FILESnon ha il 'file' chiave, il caso in cui request.FILES['file']ha None..

Come faccio a impostare i dati del post per il percorso felice? Qualcuno può dirmelo?


poiché hai contrassegnato la risposta utilizzando la classe client come corretta, probabilmente non stai cercando uno unit test, ma un test funzionale ...
Henning

Risposte:


109

Dai documenti di Django in poi Client.post:

L'invio di file è un caso speciale. Per POST un file, è necessario fornire solo il nome del campo del file come chiave e un handle di file al file che si desidera caricare come valore. Per esempio:

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

12
link al documento Django pertinente: docs.djangoproject.com/en/dev/topics/testing/overview/…
lsh


2
Henning è tecnicamente corretto - questo sarebbe più di un integration test- in realtà non importa fino a quando non si entra in basi di codice più complesse forse anche con un vero team di test
Alvin

In un framework web, fa molta meno differenza se stai testando le visualizzazioni. Ottenere la risposta tramite il client o direttamente dalla funzione è abbastanza simile da rendere valida la maggior parte dei test. Inoltre il client ti offre maggiore flessibilità. Questo è quello che uso, personalmente.
trpt4him

link aggiornamento al documento Django pertinente: docs.djangoproject.com/en/dev/topics/testing/tools/…
congelato il

109

Facevo lo stesso with open('some_file.txt') as fp:ma poi avevo bisogno di immagini, video e altri file reali nel repository e stavo anche testando una parte di un componente principale di Django che è ben testato, quindi attualmente questo è quello che ho fatto:

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+ devi usare bytesobject invece di str. Cambia "file_content"inb"file_content"

Funziona bene, SimpleUploadedFilecrea un'immagine InMemoryFileche si comporta come un normale caricamento e puoi scegliere il nome, il contenuto e il tipo di contenuto.


1
Utilizzando il tuo esempio, la convalida del modulo mi dà: "Carica un'immagine valida. Il file che hai caricato non era un'immagine o un'immagine danneggiata".
antonagestam

@antonagestam Stai passando il tipo di contenuto giusto? Il tuo modulo convalida il contenuto del file? in tal caso "file_content"deve essere un'intestazione di immagine valida in modo che il tuo codice pensi che sia un'immagine valida.
Danilo Cabello

Quali sono le intestazioni appropriate per JPEG e PNG?
antonagestam

2
Questa dovrebbe essere considerata la risposta corretta per questo problema. Grazie @DaniloCabello.
mannysz

1
Puoi utilizzare base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TUXL0YAk5 immagine reale = questa immagine.
Howdedo

6

Ti consiglio di dare un'occhiata a Django RequestFactory . È il modo migliore per simulare i dati forniti nella richiesta.

Detto questo, ho trovato diversi difetti nel tuo codice.

  • test "unità" significa testare solo una "unità" di funzionalità. Quindi, se vuoi testare quella vista, dovresti testare la vista e il file system, ergo, non proprio unit test. Per rendere più chiaro questo punto. Se esegui quel test e la vista funziona correttamente, ma non hai i permessi per salvare quel file, il tuo test fallirà per questo motivo.
  • Un'altra cosa importante è la velocità di prova . Se stai facendo qualcosa come TDD, la velocità di esecuzione dei tuoi test è molto importante. L'accesso a qualsiasi I / O non è una buona idea .

Quindi, ti consiglio di eseguire il refactoring della tua vista per utilizzare una funzione come:

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

E fai qualche beffa su questo. Puoi usare Python Mock .

PS: Potresti anche usare Django Test Client Ma ciò significherebbe che stai aggiungendo un'altra cosa in più al test, perché quel client fa uso di Sessioni, middleware, ecc. Niente di simile a Unit Testing.


1
Potrei sbagliarmi, ma sembra che intendesse test di integrazione e abbia usato il termine "test unitario" in modo errato.
jooks

1
@santiagobasulto Sono un principiante in TDD e vorrei accelerare i miei test unitari. Ma ho diverse viste che si occupano di caricamenti di file che caricano file nell'archivio remoto (Amazon S3) anche durante i test delle unità. E questo richiede tempo. Potresti espandere la tua risposta per mostrare in dettaglio come evitare di accedere all'I / O durante il test?
Dmitry Wojciechowski

5
Ehi @Dmitry. Mock è il modo per andarci. Ogni volta che devi accedere a una risorsa esterna dovresti prenderla in giro. Supponiamo di avere una vista chiamata profile_pictureche utilizza internamente una upload_profile_picturefunzione. Se vuoi testare quella vista, prendi in giro la funzione interna e assicurati che sia chiamata nel tuo test. Questo è un semplice esempio: gist.github.com/santiagobasulto/6437356
santiagobasulto

4

Faccio qualcosa di simile per la mia applicazione relativa agli eventi, ma dovresti avere un codice più che sufficiente per andare avanti con il tuo caso d'uso

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

Ho fatto qualcosa del genere:

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

La funzione create_image creerà un'immagine quindi non è necessario fornire un percorso statico dell'immagine.

Nota: è possibile aggiornare il codice secondo il codice. Questo codice per Python 3.6.


1

In Django 1.7 c'è un problema con il TestCase che può essere risolto usando open (filepath, 'rb') ma quando si usa il client di test non abbiamo alcun controllo su di esso. Penso che probabilmente sia meglio assicurarsi che file.read () restituisca sempre byte.

fonte: https://code.djangoproject.com/ticket/23912 , di KevinEtienne

Senza l'opzione rb, viene sollevata un'eccezione 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)

L'unica risposta che utilizza APIRequestFactory
majkelx

0

Come menzionato nella documentazione ufficiale di Django :

L'invio di file è un caso speciale. Per POST un file, è necessario fornire solo il nome del campo del file come chiave e un handle di file al file che si desidera caricare come valore. Per esempio:

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

Ulteriori informazioni: come verificare se il file viene passato come argomento a una funzione?

Durante il test, a volte vogliamo assicurarci che il file venga passato come argomento a qualche funzione.

per esempio

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

Nei test, usa la simulazione di Python in questo modo:

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

Spero che questo ti aiuti.


0

Sto usando Python == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0

Ho provato self.client.postma ho ottenuto Resolver404un'eccezione.

Di seguito ha funzionato per me:

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'}
    )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.