2012-06-23 19 views
56

En mi aplicación Django, tengo una visión que logra archivo upload.The núcleo fragmento es asícómo unidad de archivo de prueba de carga en Django

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

quisiera prueba de la unidad los view.I estoy planeando para probar la ruta feliz, así como la path..ie, el caso en que el request.FILES no tiene 'archivo' llave, caso en el que request.FILES['file'] tiene None fallar ..

¿Cómo puedo configurar los datos enviados por el camino feliz ? ¿Puede alguien decirme?

+0

como marcó la respuesta utilizando la clase de cliente como correcta, es probable que no esté buscando una prueba de unidad, sino una prueba funcional ... – Henning

Respuesta

79

a partir de documentos de Django en Client.post:

Submitting files is a special case. To POST a file, you need only provide the file field name as a key, and a file handle to the file you wish to upload as a value. For example:

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

enlace al documento de Django correspondiente: https://docs.djangoproject.com/ es/dev/topics/testing/overview/# django.test.client.Client.post – lsh

+2

vínculo inactivo, consulte https://docs.djangoproject.com/en/1.7/topics/testing/tools/#django.test. Client.post –

+3

En mi humilde opinión, el cliente ya no es una prueba unitaria;) – Henning

5

te recomiendo que eche un vistazo a Django RequestFactory. Es la mejor manera de burlarse de los datos proporcionados en la solicitud.

Dicho eso, encontré varios defectos en su código.

  • pruebas de "unidad" significa poner a prueba sólo una "unidad" de funcionabilidad. Por lo tanto, si quiere probar esa vista, estaría probando la vista y el archivo sistema, ergo, no es realmente una prueba unitaria. Para aclarar este punto. Si ejecuta esa prueba, y la vista funciona bien, pero no tiene permisos para guardar ese archivo, su prueba fallaría debido a eso.
  • Otra cosa importante es velocidad de prueba. Si está haciendo algo como TDD, la velocidad de ejecución de sus pruebas es realmente importante. El acceso a cualquier E/S no es una buena idea.

Por lo tanto, te recomiendo que refactor el fin de utilizar una función como:

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

y hacer algo de burla en eso. Puede usar Python Mock.

PD: También podría usar Django Test Client Pero eso significaría que está agregando otra cosa más para probar, ya que el cliente hace uso de sesiones, middleweres, etc. Nada similar a la prueba unitaria.

+1

Podría estar equivocado, pero parece que quería una prueba de integración y simplemente utilizó el término "prueba de unidad" incorrectamente. – jooks

+0

@santiagobasulto Soy un novato en TDD y me gustaría acelerar mis pruebas unitarias. Pero tengo varias vistas relacionadas con las cargas de archivos que cargan archivos en el almacenamiento remoto (Amazon S3) también durante la prueba unitaria. Eso lleva tiempo. ¿Podría ampliar su respuesta para mostrar en detalles cómo evitar el acceso a E/S durante las pruebas? –

+3

Hola @Dmitry. Mock es la manera de ir allí. Siempre que tenga que acceder a un recurso externo, debe burlarse de él. Supongamos que tiene una vista llamada 'profile_picture' que usa internamente una función' upload_profile_picture'. Si quieres probar esa vista, solo burla la función interna y asegúrate de que se invoque en tu prueba. Este es un ejemplo simple: https://gist.github.com/santiagobasulto/6437356 – santiagobasulto

4

que hacer algo como esto para mi propia aplicación relacionados con el evento, sino que debe tener más de código suficiente para seguir adelante con su propio caso de 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) 
38

solía hacer lo mismo with open('some_file.txt') as fp: pero entonces necesitaba imágenes , vídeos y otros archivos reales en el repositorio y también estaba probando una parte de un componente de la base de Django que está bien probado, por lo que actualmente esto es lo que he estado haciendo:

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

en Python 3.5 + y Es necesario utilizar el objeto bytes en lugar de str.Cambiar "file_content" a b"file_content"

Ha estado trabajando muy bien, SimpleUploadedFile crea un InMemoryFile que se comporta como una carga regular y se puede recoger el nombre, el contenido y el contenido tipo.

+0

Usando su ejemplo, la validación de formulario me da: "Cargar una imagen válida. El archivo que cargó no era una imagen o una imagen dañada". – antonagestam

+0

@antonagestam ¿Está aprobando el tipo de contenido correcto? ¿Su formulario valida el contenido del archivo? si es así, '" file_content "' debe ser un encabezado de imagen válido para que su código crea que es una imagen válida. –

+0

¿Cuáles son los encabezados apropiados para JPEG y PNG? – antonagestam

0

En Django 1.7 hay un problema con el TestCase que se puede resolver usando open (filepath, 'rb') pero al usar el cliente de prueba no tenemos control sobre él. Creo que probablemente sea mejor asegurar que file.read() devuelva siempre bytes.

fuente: https://code.djangoproject.com/ticket/23912, por KevinEtienne

Sin opción rb, una se lanza TypeError:

TypeError: sequence item 4: expected bytes, bytearray, or an object with the buffer interface, str found 
2

que hice algo por el estilo:

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

función create_image va a crear imágenes, para que no es necesario dar una ruta de imagen estática.

Nota: Puede actualizar el código según su código. Este código para Python 3.6.