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)

私はユニットテストに幸せなパスだけでなく、失敗path..ieをテストするためのview.Iアム・プランニング、ケースたいrequest.FILESどのキー「ファイル」、ケース持っていないrequest.FILES['file']持っているがNone...

ハッピーパスの投稿データを設定するにはどうすればよいですか?


クライアントクラスを使用して回答を正しいものとしてマークしたので、おそらく単体テストではなく機能テストを探しています...
Henning

回答:


109

Django docsから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


2
ヘニングは技術的には正しいです-これはより多くのintegration testことです
Alvin

Webフレームワークでは、ビューをテストしている場合、それほど大きな違いはありません。クライアントからの応答の取得と関数からの直接の取得は、ほとんどのテストを有効にするのに十分似ています。さらに、クライアントにより柔軟性が向上します。それは私が個人的に使用するものです。
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 2015年

@antonagestamあなたは正しいコンテンツタイプを渡していますか?フォームはファイルの内容を検証していますか?もしそうなら"file_content"有効な画像ヘッダーである必要があるので、コードはそれが有効な画像であると考えます。
Danilo Cabello 2015年

JPEGおよびPNGの適切なヘッダーは何ですか?
antonagestam 2015年

2
これは、この問題の正解と見なされます。@DaniloCabelloに感謝します。
mannysz

1
使用できるのは、base64.b64decode( "iVBORw0KGgoAAAANSUhEUgAAAAUA" + "AAAFCAYAAACNbyblAAAAHElEQVQI12P4 // 8 / w38GIAXDIBKE0DHxgljNBAAO" + "9TXL0Y4OHwAAAABJRU5ErkJgg image ==" as as create this image、= ")です。
Howdedo

6

Django RequestFactoryをご覧になることをお勧めします。これは、リクエストで提供されるデータを模擬するための最良の方法です。

と私はあなたのコードにいくつかの欠陥を見つけました。

  • 「ユニット」テストとは、機能の「ユニット」を1つだけテストすることを意味します。したがって、そのビューをテストする場合は、ビューと、実際には単体テストではなく、ファイルシステムergoをテストすることになります。この点をより明確にするために。そのテストを実行し、ビューが正常に機能するが、そのファイルを保存する権限がない場合、そのためにテストは失敗します。
  • その他の重要なことは、テスト速度です。TDDのようなことをしている場合、テストの実行速度は本当に重要です。 I / Oにアクセスすることはお勧めできません

したがって、次のような関数を使用するようにビューをリファクタリングすることをお勧めします。

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

そして、それをあざける。Python Mockを使用できます。

PS:Django Test Clientを使用することもできますが、そのクライアントはセッションやミドルウェアなどを利用するため、テストするものをさらに追加することになります。単体テストとは似ていません。


1
私は間違っている可能性がありますが、彼は統合テストを意図していて、「単体テスト」という用語を誤って使用しているようです。
ジョーク2013

1
@santiagobasulto私はTDDの初心者です。ユニットテストを高速化したいと考えています。しかし、私はファイルのアップロードを扱ういくつかのビューを持っています。これらは、単体テスト中にリモートストレージ(Amazon S3)にファイルをアップロードします。時間がかかります。テスト中にI / Oへのアクセスを回避する方法を詳細に示すために、回答を拡張していただけませんか?
Dmitry Wojciechowski 2013

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を利用する唯一の答え
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)
        ...

テストでは、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'}
    )
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.