Як розділити тестовий файл завантаження в django


99

У моєму додатку джанго я маю вигляд, який здійснює завантаження файлу. Основний фрагмент такий

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

Подання файлів - особливий випадок. Щоб розмістити файл, вам потрібно вказати лише ім'я поля файлу як ключ, а обробку файлу - файл, який ви хочете завантажити як значення. Наприклад:

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/…
Jocelyn delalande

2
Хеннінг є технічно правильним - це було б більше ніж integration test- насправді не має значення, поки ти не потрапиш у складніші кодові бази, можливо, навіть із фактичною командою тестування
Елвін

У веб-рамках це значно менше значення, якщо ви тестуєте представлення даних. Отримання відповіді через клієнта порівняно безпосередньо з функцією досить схоже, щоб більшість тестів були дійсними. Плюс клієнт надає вам більше гнучкості. Ось чим я особисто користуюся.
trpt4him

оновлення посилань на відповідний документ Django: docs.djangoproject.com/en/dev/topics/testing/tools/…
заморожено

109

Раніше я робив те саме, 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"повинен бути дійсним заголовком зображення, щоб ваш код вважав, що це дійсне зображення.
Данило Кабелло

Які відповідні заголовки для JPEG та PNG?
antonagestam

2
Це слід вважати правильною відповіддю на цю проблему. Дякую @DaniloCabello
mannysz

1
Ви можете використовувати base64.b64decode ( "iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXL0Y4OHwAAAABJRU5ErkJggg ==") в якості змісту зображення, це створює реальне зображення.
Howdedo

6

Рекомендую поглянути на Джанго RequestFactory . Це найкращий спосіб знущатися з даних, наданих у запиті.

Сказав це, я знайшов кілька вад у вашому коді.

  • "одиниця" тестування означає перевірити лише один "блок" функціональності. Отже, якщо ви хочете протестувати цей вид, ви б тестували подання, а файлова система, ergo, насправді не є тестовою одиницею. Щоб зробити це більш зрозумілим. Якщо ви запустите цей тест, і перегляд працює добре, але у вас немає дозволів на збереження цього файлу, ваш тест через це не вдасться.
  • Інша важлива річ - швидкість тесту . Якщо ви робите щось на кшталт TDD, швидкість виконання ваших тестів дійсно важлива. Доступ до будь-якого вводу-виводу не є хорошою ідеєю .

Отже, я рекомендую вам переробити перегляд, щоб скористатися такою функцією, як:

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

І зробіть знущання над цим. Ви можете використовувати Python Mock .

PS: Ви також можете користуватися тестовим клієнтом Django, але це означає, що ви додаєте ще одне, що потрібно для тестування, оскільки цей клієнт використовує сеанси, середні програми тощо. Нічого подібного до тестування модулів.


1
Я можу помилятися, але, схоже, він мав на увазі тест на інтеграцію і просто неправильно вжив термін 'блок тесту'.
жарти

1
@santiagobasulto Я новачок в TDD і хотів би прискорити тестування. Але у мене є кілька поглядів, які стосуються завантаження файлів, які завантажують файли у віддалене сховище (Amazon S3) і під час тестування одиниць. Це вимагає часу. Чи можете ви розширити свою відповідь, щоб детально показати, як уникнути доступу до вводу-виводу під час тестування?
Дмитро Войцеховський

5
Гей @Dmitry. Макет - це шлях туди. Щоразу, коли вам доводиться отримувати доступ до зовнішнього ресурсу, слід знущатися над ним. Припустимо, у вас є вид, який називається 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)

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
майкелькс

0

Як зазначено в офіційній документації Джанга :

Подання файлів - особливий випадок. Щоб розмістити файл, вам потрібно вказати лише ім'я поля файлу як ключ, а обробку файлу - файл, який ви хочете завантажити як значення. Наприклад:

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

У тестах використовуйте макет 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'}
    )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.