web-dev-qa-db-fra.com

comment télécharger des fichiers de test unitaires au Django

Dans mon application Django, j'ai une vue qui permet de télécharger des fichiers. L'extrait de code 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 souhaite tester un peu la vue. Je prévois de tester le chemin heureux ainsi que le chemin d'échec ... c'est-à-dire le cas où le request.FILES n'a pas de clé 'fichier', le cas où request.FILES['file'] a None ..

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

72
damon

Depuis Django docs sur Client.post :

La soumission de fichiers est un cas particulier. Pour POST un fichier, vous avez seulement besoin de fournissez le nom du champ de fichier en tant que clé et un descripteur de fichier au fichier 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})
94
Arthur Neves

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 fichiers réels dans le référentiel et je testais également une partie d'un composant de base de Django qui est bien testé. C'est donc ce que je fais actuellement:

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 objet bytes au lieu de str. Remplacez "file_content" par b"file_content"

Cela fonctionne très bien, SimpleUploadedFile crée une InMemoryFile qui se comporte comme un téléchargement normal et vous pouvez choisir le nom, le contenu et le type de contenu.

68
Danilo Cabello

Je vous recommande de consulter Django RequestFactory . C'est le meilleur moyen de simuler les données fournies dans la demande.

Cela dit, j'ai trouvé plusieurs failles dans votre code.

  • Le test "unité" signifie tester juste un "unité" de fonctionnalité. Ainsi, Si vous voulez tester cette vue, vous testez celle-ci et le fichier Système, de manière ergo, pas vraiment 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 Pour enregistrer ce fichier, votre test échouera à cause de cela.
  • Une autre chose importante est vitesse de test. Si vous faites quelque chose comme TDD, la rapidité d'exécution de vos tests est vraiment importante .Accéder à une entrée/sortie 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 fais quelques moqueurs là-dessus. Vous pouvez utiliser Python Mock .

PS: Vous pouvez également utiliser Django Test Client Mais cela voudrait dire que vous ajouterez autre chose à tester, car ce client utilisera des sessions, des réponses intermédiaires, etc. Rien de semblable au test unitaire.

6
santiagobasulto

Je fais quelque chose comme ceci pour ma propre application liée à un événement, mais vous devriez avoir suffisamment de code pour passer à 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
super9

Dans Django 1.7, TestCase pose un problème qui peut être résolu en utilisant open (chemin du fichier, 'rb'), mais en utilisant le client de test, nous n'avons aucun contrôle sur celui-ci. 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 erreur TypeError est générée:

TypeError: sequence item 4: expected bytes, bytearray, or an object with the buffer interface, str found
1
Rômulo Collopy
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)
0
Suvodeep Dubey

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 de sorte que vous n'ayez pas besoin de donner un chemin d'image statique.

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

0
Chirag Maliwal