как загрузить файл модульного теста в django

99

В моем приложении 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)

Я хотел бы провести модульное тестирование представления. Я планирую протестировать счастливый путь, а также путь сбоя ... т.е. случай, когда у request.FILESнего нет ключевого файла, случай, когда request.FILES['file']есть None..

Как мне настроить данные поста для счастливого пути? Может кто-нибудь подскажет?

Деймон
источник
поскольку вы отметили ответ, используя класс клиента как правильный, вы, вероятно, ищете не модульный тест, а функциональный тест ...
Хеннинг,

Ответы:

109

Из документов Django Client.post:

Отправка файлов - особый случай. Чтобы отправить файл POST, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:

c = Client()
with open('wishlist.doc') as fp:
  c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
Артур Невес
источник
12
ссылка на соответствующий документ Django: docs.djangoproject.com/en/dev/topics/testing/overview/…
lsh
5
мертвая ссылка, см. docs.djangoproject.com/en/1.7/topics/testing/tools/…
Джоселин
2
Хеннинг технически прав - это было бы больше integration test- на самом деле не имеет значения, пока вы не перейдете к более сложным кодовым базам, возможно, даже с реальной командой тестирования
Элвин
В веб-фреймворке гораздо меньше разницы, если вы тестируете представления. Получение ответа через клиента или напрямую из функции достаточно похоже, чтобы большинство тестов было действительным. К тому же клиент дает вам больше гибкости. Это то, что я использую лично.
trpt4him
обновление ссылки на соответствующий документ Джанго: docs.djangoproject.com/en/dev/topics/testing/tools/...
заморожена
112

Раньше я делал то же самое, with open('some_file.txt') as fp:но потом мне понадобились изображения, видео и другие реальные файлы в репозитории, а также я тестировал часть основного компонента Django, который хорошо протестирован, поэтому в настоящее время я делаю вот что:

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

В Python 3.5+ вам нужно использовать bytesобъект вместо str. Изменить "file_content"наb"file_content"

Он работает нормально, SimpleUploadedFileсоздает файл, InMemoryFileкоторый ведет себя как обычная загрузка, и вы можете выбрать имя, контент и тип контента.

Данило Кабельо
источник
1
Используя ваш пример, проверка формы дает мне: «Загрузите действительное изображение. Загруженный вами файл не был изображением или поврежденным изображением».
antonagestam
@antonagestam Вы передаете правильный тип контента? Ваша форма проверяет содержимое файла? если это "file_content"необходимо, должен быть действительный заголовок изображения, чтобы ваш код считал его действительным изображением.
Danilo Cabello
Какие заголовки подходят для JPEG и PNG?
antonagestam
2
Это следует считать правильным ответом на данную проблему. Спасибо @DaniloCabello.
Mannysz 03
1
Вы можете использовать base64.b64decode ("iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXLAAAAAO" + "9TXLAAJ4JOHW как изображение.
Howdedo,
6

Я рекомендую вам взглянуть на Django RequestFactory . Это лучший способ имитировать данные, указанные в запросе.

Сказал, что я нашел несколько недостатков в вашем коде.

  • «Модульное» тестирование означает тестирование только одной «единицы» функциональности. Итак, если вы хотите протестировать это представление, вы должны тестировать представление и файловую систему, следовательно, не совсем модульный тест. Чтобы прояснить этот момент. Если вы запустите этот тест, и представление работает нормально, но у вас нет разрешений на сохранение этого файла, ваш тест не удастся из-за этого.
  • Другой важный момент - тестовая скорость . Если вы делаете что-то вроде TDD, скорость выполнения ваших тестов действительно важна. Доступ к любому вводу-выводу - не лучшая идея .

Итак, я рекомендую вам провести рефакторинг вашего представления, чтобы использовать такую ​​функцию, как:

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

И посмеемся над этим. Вы можете использовать Python Mock .

PS: Вы также можете использовать Django Test Client, но это будет означать, что вы добавляете еще одну вещь для тестирования, потому что этот клиент использует сеансы, промежуточное программное обеспечение и т. Д. Ничего похожего на модульное тестирование.

Сантьягобасульто
источник
1
Я могу ошибаться, но похоже, что он имел в виду интеграционный тест и просто неправильно использовал термин «модульный тест».
jooks
1
@santiagobasulto Я новичок в TDD и хотел бы ускорить модульное тестирование. Но у меня есть несколько представлений, касающихся загрузки файлов, которые также загружают файлы в удаленное хранилище (Amazon S3) во время модульного тестирования. Это требует времени. Не могли бы вы расширить свой ответ, чтобы подробно показать, как избежать доступа к вводу-выводу во время тестирования?
Дмитрий Войцеховский
5
Привет @ Дмитрий. Mock - это путь туда. Всякий раз, когда вам нужно получить доступ к внешнему ресурсу, вы должны издеваться над ним. Предположим, у вас есть представление, profile_pictureкоторое внутренне использует upload_profile_pictureфункцию. Если вы хотите протестировать это представление, просто имитируйте внутреннюю функцию и убедитесь, что она вызывается в вашем тесте. Это простой пример: gist.github.com/santiagobasulto/6437356
santiagobasulto
4

Я делаю что-то подобное для своего собственного приложения, связанного с событиями, но у вас должно быть более чем достаточно кода, чтобы продолжить свой собственный вариант использования.

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
источник
4

Я сделал что-то подобное:

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

Функция create_image создаст изображение, поэтому вам не нужно указывать статический путь изображения.

Примечание: вы можете обновить код в соответствии с вашим кодом. Этот код для Python 3.6.

Чираг Маливал
источник
1

В Django 1.7 есть проблема с TestCase, которая может быть решена с помощью open (filepath, 'rb'), но при использовании тестового клиента мы не контролируем его. Я думаю, что лучше всего убедиться, что file.read () всегда возвращает байты.

источник: https://code.djangoproject.com/ticket/23912 , автор KevinEtienne

Без параметра rb возникает ошибка 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)
Суводип Дубей
источник
Единственный ответ, использующий APIRequestFactory
majkelx
0

Как упоминалось в официальной документации Django :

Отправка файлов - особый случай. Чтобы отправить файл POST, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:

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

Дополнительная информация: как проверить, передан ли файл в качестве аргумента какой-либо функции?

Во время тестирования иногда мы хотим убедиться, что файл передается в качестве аргумента какой-либо функции.

например

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

В тестах используйте mock Python примерно так:

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

Надеюсь это поможет.

Тобиас Эрнст
источник
0

Я использую Python == 3.8.2, Django == 3.0.4, djangorestframework == 3.11.0

Я пробовал, self.client.postно получилResolver404 исключение.

Для меня сработало следующее:

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
источник