jak przesłać plik testu jednostkowego w django

99

W mojej aplikacji django mam widok, który umożliwia przesyłanie plików, a fragment rdzenia wygląda tak

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

Chciałbym przetestować widok jednostkowy. Planuję przetestować zarówno ścieżkę szczęśliwą, jak i ścieżkę niepowodzenia ... tj. Przypadek, w którym request.FILESplik nie ma klucza, przypadek, w którym request.FILES['file']ma None...

Jak skonfigurować dane posta dla szczęśliwej ścieżki? Czy ktoś może mi powiedzieć?

damon
źródło
ponieważ oznaczyłeś odpowiedź za pomocą klasy klienta jako poprawną, prawdopodobnie nie szukasz testu jednostkowego, ale testu funkcjonalnego ...
Henning

Odpowiedzi:

109

Z dokumentacji Django na Client.post:

Przesyłanie plików to szczególny przypadek. Aby POSTOWAĆ plik, wystarczy podać nazwę pola pliku jako klucz i uchwyt pliku do pliku, który chcesz przesłać jako wartość. Na przykład:

c = Client()
with open('wishlist.doc') as fp:
  c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
Arthur Neves
źródło
12
link do odpowiedniego dokumentu Django: docs.djangoproject.com/en/dev/topics/testing/overview/ ...
lsh
5
martwy link, patrz docs.djangoproject.com/en/1.7/topics/testing/tools/ ...
Jocelyn delalande
2
Henning jest technicznie poprawny - to byłoby bardziej integration test- nie ma znaczenia, dopóki nie przejdziesz do bardziej złożonych baz kodu, może nawet z prawdziwym zespołem testowym
Alvin
W frameworku sieciowym ma znacznie mniejsze znaczenie, jeśli testujesz widoki. Uzyskanie odpowiedzi przez klienta vs. bezpośrednio z funkcji jest na tyle podobne, że większość testów jest poprawna. Ponadto klient zapewnia większą elastyczność. To jest to, czego używam osobiście.
trpt4him
Aktualizacja link do odpowiedniej Django doc: docs.djangoproject.com/en/dev/topics/testing/tools/...
zamarznięty
109

Kiedyś robiłem to samo, with open('some_file.txt') as fp:ale potem potrzebowałem obrazów, filmów i innych prawdziwych plików w repozytorium, a także testowałem część podstawowego komponentu Django, który jest dobrze przetestowany, więc obecnie robię to:

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

W Pythonie 3.5+ musisz użyć bytesobject zamiast str. Zmiana"file_content" nab"file_content"

Działa dobrze, SimpleUploadedFiletworzy plik, InMemoryFilektóry zachowuje się jak zwykłe przesyłanie i możesz wybrać nazwę, zawartość i typ treści.

Danilo Cabello
źródło
1
Korzystając z Twojego przykładu, weryfikacja formularza daje mi: „Prześlij prawidłowy obraz. Przesłany plik nie jest obrazem lub jest uszkodzony”.
antonagestam
@antonagestam Czy przekazujesz właściwy typ treści? Czy Twój formularz weryfikuje zawartość pliku? jeśli tak, "file_content"musi to być prawidłowy nagłówek obrazu, aby kod pomyślał, że jest to prawidłowy obraz.
Danilo Cabello
Jakie są odpowiednie nagłówki dla plików JPEG i PNG?
antonagestam
2
Należy to uznać za poprawną odpowiedź na ten problem. Dzięki @DaniloCabello.
mannysz
1
Możesz użyć base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXLArk = ten obraz.
Howdedo
6

Polecam zapoznać się z Django RequestFactory . To najlepszy sposób na mockowanie danych podanych w żądaniu.

Powiedział, że znalazłem kilka błędów w twoim kodzie.

  • Testowanie „jednostkowe” oznacza testowanie tylko jednej „jednostki” funkcjonalności. Tak więc, jeśli chcesz przetestować ten widok, przetestowałbyś widok i system plików, ergo, a nie tak naprawdę test jednostkowy. Aby to wyjaśnić. Jeśli uruchomisz ten test, a widok działa dobrze, ale nie masz uprawnień do zapisania tego pliku, test zakończy się niepowodzeniem.
  • Inną ważną rzeczą jest szybkość testowania . Jeśli robisz coś takiego jak TDD, szybkość wykonywania testów jest naprawdę ważna. Dostęp do dowolnego wejścia / wyjścia nie jest dobrym pomysłem .

Dlatego zalecam zmianę widoku, aby użyć funkcji takiej jak:

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

I zrób z tego trochę kpin. Możesz użyć Python Mock .

PS: Możesz także użyć klienta testowego Django. Ale to oznaczałoby, że dodajesz jeszcze jedną rzecz do testowania, ponieważ ten klient korzysta z sesji, oprogramowania pośredniego itp. Nie ma nic podobnego do testowania jednostkowego.

santiagobasulto
źródło
1
Mogę się mylić, ale wygląda na to, że zamierzał przetestować integrację i po prostu niepoprawnie użył terminu „test jednostkowy”.
jooks
1
@santiagobasulto Jestem nowicjuszem w TDD i chciałbym przyspieszyć moje testy jednostkowe. Ale mam kilka poglądów dotyczących przesyłania plików, które przesyłają pliki do zdalnego magazynu (Amazon S3) również podczas testów jednostkowych. A to wymaga czasu. Czy mógłbyś rozszerzyć swoją odpowiedź, aby szczegółowo pokazać, jak uniknąć dostępu do wejścia / wyjścia podczas testowania?
Dmitry Wojciechowski
5
Hej @Dmitry. Mock to najlepszy sposób, aby się tam dostać. Zawsze, gdy masz dostęp do zewnętrznego zasobu, powinieneś z niego kpić. Załóżmy, że masz widok o nazwie, profile_picturektóry wewnętrznie używa upload_profile_picturefunkcji. Jeśli chcesz przetestować ten widok, po prostu mock funkcję wewnętrzną i upewnij się, że jest wywoływana w twoim teście. Oto prosty przykład: gist.github.com/santiagobasulto/6437356
santiagobasulto
4

Robię coś takiego dla mojej własnej aplikacji związanej z wydarzeniami, ale powinieneś mieć więcej niż wystarczającą ilość kodu, aby zająć się własnym przypadkiem użycia

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)
super9
źródło
4

Zrobiłem coś takiego:

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

Funkcja create_image utworzy obraz, więc nie musisz podawać statycznej ścieżki obrazu.

Uwaga: możesz zaktualizować kod zgodnie z kodem. Ten kod dla Pythona 3.6.

Chirag Maliwal
źródło
1

W Django 1.7 występuje problem z TestCase, który można rozwiązać za pomocą open (filepath, 'rb'), ale podczas korzystania z klienta testowego nie mamy nad nim kontroli. Myślę, że prawdopodobnie najlepiej jest upewnić się, że funkcja file.read () zwraca zawsze bajty.

źródło: https://code.djangoproject.com/ticket/23912 , KevinEtienne

Bez opcji rb generowany jest błąd TypeError:

TypeError: sequence item 4: expected bytes, bytearray, or an object with the buffer interface, str found
Rômulo Collopy
źródło
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)
Suvodeep Dubey
źródło
Jedyna odpowiedź wykorzystująca APIRequestFactory
majkelx
0

Jak wspomniano w oficjalnej dokumentacji Django :

Przesyłanie plików to szczególny przypadek. Aby POSTOWAĆ plik, wystarczy podać nazwę pola pliku jako klucz i uchwyt pliku do pliku, który chcesz przesłać jako wartość. Na przykład:

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

Więcej informacji: Jak sprawdzić, czy plik jest przekazywany jako argument do jakiejś funkcji?

Podczas testowania czasami chcemy się upewnić, że plik jest przekazywany jako argument do jakiejś funkcji.

na przykład

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

W testach użyj makiety Pythona, czegoś takiego:

# 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']),
)
Dipen Dadhaniya
źródło
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})

Mam nadzieję że to pomoże.

Tobias Ernst
źródło
0

Używam Pythona == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0

Próbowałem, self.client.postale dostałem Resolver404wyjątek.

Pracowały dla mnie następujące:

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'}
    )
Aseem
źródło