テスト用に何らかのMockMixin
を作成する必要があります。外部ソースを呼び出すすべてのもののモックを含める必要があります。たとえば、管理パネルでモデルを保存するたびに、いくつかのリモートURLを呼び出します。それをあざけってそのように使うのは良いことです:
class ExampleTestCase(MockedTestCase):
# tests
したがって、機能テストなどの管理でモデルを保存するたびに、リモートURLを呼び出す代わりにこのモックが適用されます。
それは実際に可能ですか?私は1つの特定のテストでそれを行うことができますが、それは問題ではありません。しかし、私はそれを頻繁に使用するので、いくつかのグローバルなモックがある方が便利でしょう。
mock
documentation によれば:
パッチは、TestCaseクラスデコレータとして使用できます。クラス内の各テストメソッドを装飾することで機能します。これにより、テストメソッドが共通のパッチセットを共有する場合のボイラープレートコードが削減されます。
これは基本的に、内部のすべてのテストメソッドが実行されている間に外部呼び出しを模擬する_@patch
_デコレータが適用された基本テストクラスを作成できることを意味します。
また、 start()
およびstop()
パッチャーのメソッドをそれぞれsetUp()
およびtearDown()
メソッドで使用できます:
_class BaseTestCase(TestCase):
def setUp(self):
self.patcher = patch('mymodule.foo')
self.mock_foo = self.patcher.start()
def tearDown(self):
self.patcher.stop()
_
alecxe's answer に追加するだけですが、teardown()
を使用している場合は ドキュメントに従って
stop
を呼び出して、パッチが「取り消され」ていることを確認する必要があります。setUp
で例外が発生した場合、tearDown
が呼び出されないため、これは想像以上に手間がかかる可能性があります。
テストで例外が発生した場合、パッチは元に戻されません。より良い方法は、addCleanup()
内で setUp()
を呼び出すことです。次に、tearDown()
メソッドを完全に省略できます。
class BaseTestCase(TestCase):
def setUp(self):
self.patcher = patch('mymodule.foo')
self.mock_foo = self.patcher.start()
self.addCleanup(self.patcher.stop) # add this line
結局、目的を果たすためのテストランナーを作成しました。テスト中にイメージが実際にファイルシステムに書き込まれないように、ファイルストレージをモックする必要がありました。画像オブジェクトは多くのテストで呼び出されているため、各クラスにパッチを適用することはDRY
ではありません。また、テストが失敗した場合に備えて、ファイル自体をモックするとシステム上に残ることに気づきました。しかし、この方法はそうではありませんでした。
プロジェクトルートにrunner.py
ファイルを作成しました
# runner.py
from unittest.mock import patch
from Django.test.runner import DiscoverRunner
from myapp.factories import ImageFactory
class UnitTestRunner(DiscoverRunner):
@patch('Django.core.files.storage.FileSystemStorage.save')
def run_tests(self, test_labels, mock_save, extra_tests=None, **kwargs):
mock_save.return_value = ImageFactory.get_image()
return super().run_tests(test_labels, extra_tests=None, **kwargs)
次に、python manage.py tests --testrunner=runner.UnitTestRunner
を使用してテストを実行します
わかりやすくするために、ImageFactory.get_image
メソッドはカスタムメソッドです
from Django.core.files.base import ContentFile
from factory.Django import DjangoModelFactory
from io import BytesIO
from PIL import Image as PilImage
from random import randint
class ImageFactory(DjangoModelFactory):
@classmethod
def get_image(cls, name='trial', extension='png', size=None):
if size is None:
width = randint(20, 1000)
height = randint(20, 1000)
size = (width, height)
color = (256, 0, 0)
file_obj = BytesIO()
image = PilImage.new("RGBA", size=size, color=color)
image.save(file_obj, extension)
file_obj.seek(0)
return ContentFile(file_obj.read(), f'{name}.{extension}')