comment télécharger un fichier de test unitaire dans django


99

Dans mon application django, j'ai une vue qui accomplit le téléchargement de fichier. L'extrait de base est comme ceci

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

Je voudrais tester la vue unitaire.Je prévois de tester le chemin heureux ainsi que le chemin d'échec..ie, le cas où le request.FILES'fichier' n'a pas de clé, cas où request.FILES['file']a None..

Comment configurer les données de publication pour le chemin heureux? Quelqu'un peut-il me le dire?


comme vous avez marqué la réponse en utilisant la classe client comme correcte, vous ne recherchez probablement pas un test unitaire, mais un test fonctionnel ...
Henning

Réponses:


109

À partir de la documentation Django sur Client.post:

La soumission de fichiers est un cas particulier. Pour POSTER un fichier, il vous suffit de fournir le nom du champ de fichier en tant que clé et un descripteur de fichier au fichier que vous souhaitez télécharger en tant que valeur. Par exemple:

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

12
lien vers le document Django pertinent: docs.djangoproject.com/en/dev/topics/testing/overview/…
lsh


2
Henning est techniquement correct - ce serait plus un integration test- n'a pas vraiment d'importance tant que vous n'entrez pas dans des bases de code plus complexes, peut-être même avec une équipe de test réelle
Alvin

Dans un framework Web, cela fait beaucoup moins de différence si vous testez des vues. Obtenir la réponse via le client et directement depuis la fonction est assez similaire pour que la plupart des tests soient valides. De plus, le client vous offre plus de flexibilité. C'est ce que j'utilise personnellement.
trpt4him

lien de mise à jour vers le document Django pertinent: docs.djangoproject.com/en/dev/topics/testing/tools/…
gelé le

109

J'avais l'habitude de faire la même chose with open('some_file.txt') as fp:mais j'avais besoin d'images, de vidéos et d'autres vrais fichiers dans le repo et aussi je testais une partie d'un composant de base de Django qui est bien testé, donc actuellement, c'est ce que je fais:

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

Dans Python 3.5+, vous devez utiliser un bytesobjet au lieu de str. Changer "file_content"pourb"file_content"

Cela fonctionne bien, SimpleUploadedFilecrée un InMemoryFilequi se comporte comme un téléchargement normal et vous pouvez choisir le nom, le contenu et le type de contenu.


1
En utilisant votre exemple, la validation du formulaire me donne: "Téléchargez une image valide. Le fichier que vous avez téléchargé n'était pas une image ou une image corrompue."
antonagestam

@antonagestam Passez-vous le bon type de contenu? Votre formulaire valide-t-il le contenu du fichier? si c'est le cas, il "file_content"doit s'agir d'un en-tête d'image valide pour que votre code pense que c'est une image valide.
Danilo Cabello

Quels sont les en-têtes appropriés pour JPEG et PNG?
antonagestam

2
Cela devrait être considéré comme la bonne réponse à ce problème. Merci @DaniloCabello.
mannysz

1
Vous pouvez utiliser base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXL0Ygg4OHwAAAJRg une image réelle = cette image crée un contenu réel".
Howdedo

6

Je vous recommande de jeter un œil à Django RequestFactory . C'est le meilleur moyen de simuler les données fournies dans la demande.

Dit cela, j'ai trouvé plusieurs failles dans votre code.

  • Le test "unitaire" signifie tester une seule "unité" de fonctionnalité. Donc, si vous voulez tester cette vue, vous testerez la vue et le système de fichiers, ergo, pas vraiment un test unitaire. Pour rendre ce point plus clair. Si vous exécutez ce test et que la vue fonctionne correctement, mais que vous ne disposez pas des autorisations nécessaires pour enregistrer ce fichier, votre test échouera à cause de cela.
  • Une autre chose importante est la vitesse du test . Si vous faites quelque chose comme TDD, la vitesse d'exécution de vos tests est vraiment importante. L'accès à n'importe quelle E / S n'est pas une bonne idée .

Donc, je vous recommande de refactoriser votre vue pour utiliser une fonction comme:

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

Et moquez-vous de cela. Vous pouvez utiliser Python Mock .

PS: Vous pouvez également utiliser Django Test Client Mais cela voudrait dire que vous ajoutez autre chose à tester, car ce client utilise des sessions, des middlewares, etc. Rien de tel que les tests unitaires.


1
Je peux me tromper, mais il semble qu'il voulait faire un test d'intégration et qu'il ait utilisé le terme «test unitaire» de manière incorrecte.
jooks

1
@santiagobasulto Je suis un débutant en TDD et j'aimerais accélérer mes tests unitaires. Mais j'ai plusieurs vues traitant des téléchargements de fichiers qui téléchargent également des fichiers sur le stockage distant (Amazon S3) pendant les tests unitaires. Un qui prend du temps. Pourriez-vous s'il vous plaît développer votre réponse pour montrer en détail comment éviter d'accéder aux E / S pendant les tests?
Dmitry Wojciechowski

5
Salut @Dmitry. Mock est le moyen d'y aller. Chaque fois que vous devez accéder à une ressource externe, vous devez vous en moquer. Supposons que vous ayez une vue appelée profile_picturequi utilise une upload_profile_picturefonction en interne . Si vous souhaitez tester cette vue, simulez simplement la fonction interne et assurez-vous qu'elle est appelée lors de votre test. Voici un exemple simple: gist.github.com/santiagobasulto/6437356
santiagobasulto

4

Je fais quelque chose comme ça pour ma propre application liée aux événements, mais vous devriez avoir plus que suffisamment de code pour continuer avec votre propre cas d'utilisation

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

J'ai fait quelque chose comme ça:

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 fonction create_image créera une image afin que vous n'ayez pas besoin de donner le chemin statique de l'image.

Remarque: vous pouvez mettre à jour le code selon votre code. Ce code pour Python 3.6.


1

Dans Django 1.7, il y a un problème avec TestCase qui peut être résolu en utilisant open (filepath, 'rb') mais lorsque nous utilisons le client de test, nous n'avons aucun contrôle dessus. Je pense qu'il est probablement préférable de s'assurer que file.read () renvoie toujours des octets.

source: https://code.djangoproject.com/ticket/23912 , par KevinEtienne

Sans l'option rb, une TypeError est déclenchée:

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)

La seule réponse utilisant APIRequestFactory
majkelx

0

Comme mentionné dans la documentation officielle de Django :

La soumission de fichiers est un cas particulier. Pour POSTER un fichier, il vous suffit de fournir le nom du champ de fichier en tant que clé et un descripteur de fichier au fichier que vous souhaitez télécharger en tant que valeur. Par exemple:

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

Plus d'informations: Comment vérifier si le fichier est passé en argument à une fonction?

Lors du test, nous voulons parfois nous assurer que le fichier est passé comme argument à une fonction.

par exemple

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

Dans les tests, utilisez la simulation de Python comme ceci:

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

J'espère que cela t'aides.


0

J'utilise Python == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0

J'ai essayé self.client.postmais j'ai eu une Resolver404exception.

La suite a fonctionné pour moi:

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'}
    )
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.