本記事について
本記事では、ユニットテストの対象となるメソッド内で呼び出している各種メソッドを、pytestのmocker fixureを使用してpatchを適用し、テスト対象となる処理をMock化する場合の実装サンプルを記載しています。
本記事の作成環境
- Ubuntu 20.04.3 LTS on WSL2
- Python: 3.9.7
- pytest: 7.1.2
- pytest-mock: 3.7.0
patchの適用によるテスト対象のMock化
クラスオブジェクトに対してpatchを適用
クラスオブジェクトに対してmocker.patch()
、またはmocker.patch.object()
を使用して、patchを適用します。
クラスオブジェクトにpatchを適用するため、テストメソッドを終了するまでは、以降は常にMock化した値を返却します。
クラスメソッド / 静的メソッド
テスト対象メソッド
pathlib.Path
クラスのクラスメソッドである、cwd()
にpatchを適用してテストを実施します。
本手順ではクラスメソッドにpatchを適用していますが、静的メソッドに対しても同様の手順でpatchの適用が可能です。
from pathlib import Path
class Foo:
@staticmethod
def get_current_directory():
return Path.cwd()
クラスオブジェクトを指定してpatchを適用
pathlib.Path
クラスオブジェクトのcwd()
メソッドをtargetに指定してpatchを適用します。
オブジェクトを対象とするため、mocker.patch.object()
を使用します。
# pathlib.Pathにpatchを適用するため、Pathのimportが必要
from pathlib import Path
from sample.foo import Foo
def test_get_current_directory(mocker):
path = Path("current_dir/")
# Pathクラスオブジェクトのcwd()にpatchを適用
mocker.patch.object(Path, "cwd", return_value=path)
assert Foo.get_current_directory() == path
メソッドを指定してpatchを適用
pathlib.Path.cwd()
メソッドをtargetに指定してpatchを適用します。
メソッドを対象とするため、mocker.patch()
を使用します。
from sample.foo import Foo
def test_get_current_directory(mocker):
path = Path("current_dir/")
# sample.fooモジュールにimportされているPath.cwd()にpatchを適用
mocker.patch("sample.foo.Path.cwd", return_value=path)
assert Foo.get_current_directory() == path
インスタンスメソッド
pathlib.Path
クラスのインスタンスメソッドである、is_file()
にpatchを適用してテストを実施します。
テスト対象メソッド
from pathlib import Path
class Foo:
def __init__(self):
self._work_directory = Path("./work")
self._work_file = self._work_directory / Path("work_file")
def has_work_file(self):
return self._work_file.is_file()
クラスオブジェクトを指定してpatchを適用
pathlib.Path
クラスオブジェクトのis_file()
メソッドをtargetに指定してpatchを適用します。
オブジェクトを対象とするため、mocker.patch.object()
を使用します。
# pathlib.Pathにpatchを適用するため、Pathのimportが必要
from pathlib import Path
from sample.foo import Foo
def test_has_file(mocker):
# Pathクラスオブジェクトのis_file()にpatchを適用
mocker.patch.object(Path, "is_file", return_value=True)
foo = Foo()
assert foo.has_work_file() == True
メソッドを指定してpatchを適用
pathlib.Path.is_file()
メソッドをtargetに指定してpatchを適用します。
メソッドを対象とするため、mocker.patch()
を使用します。
from sample.foo import Foo
def test_has_file(mocker):
foo = Foo()
# sample.fooモジュールにimportされているPath.is_file()にpatchを適用
mocker.patch("sample.foo.Path.is_file", return_value=True)
assert foo.has_work_file() == True
プロパティ
pathlib.Path
クラスのプロパティである、name
にpatchを適用してテストを実施します。
from pathlib import Path
class Foo:
def __init__(self):
self._work_directory = Path("./work")
self._work_file = self._work_directory / Path("work_file")
def get_work_file_name(self):
return self._work_file.name
クラスオブジェクトを指定してpatchを適用
pathlib.Path
クラスオブジェクトのname
プロパティをtargetに指定してpatchを適用します。
オブジェクトを対象とするため、mocker.patch.object()
を使用します。
その際、プロパティに対してpatchを適用するため、new_callable
でPropertyMock
を渡します。
# pathlib.Pathにpatchを適用するため、Pathのimportが必要
from pathlib import Path
from sample.foo import Foo
def test_get_work_file_name(mocker):
foo = Foo()
# Pathクラスオブジェクトのis_file()にpatchを適用
# プロパティ
mocker.patch.object(
Path,
"name",
new_callable=mocker.PropertyMock,
return_value="mocked_work_file")
assert foo.get_work_file_name() == "mocked_work_file"
インスタンスオブジェクトに対してpatchを適用
インスタンスオブジェクトに対しても、クラスオブジェクトと同様にmocker.patch()
、またはmocker.patch.object()
を使用してpatchを適用することが可能です。
しかし、実際にインスタンスオブジェクトに対してpatchを適用しようとすると、エラーが発生してpatchを適用できない場合があります。1
その場合は、patchを適用するインスタンスオブジェクトをMockに置き換え、patchを適用します。
インスタンスオブジェクトをMockに置き換えているため、そのインスタンスオブジェクトのみMock化した値を返却します。
インスタンスメソッド
pathlib.Path
クラスのインスタンスメソッドである、is_file()
にpatchを適用してテストを実施します。
テスト対象メソッド
from pathlib import Path
class Foo:
def __init__(self):
self._work_directory = Path("./work")
self._work_file = self._work_directory / Path("work_file")
def has_work_file(self):
return self._work_file.is_file()
インスタンスオブジェクトをMockに置き換えてpatchを適用
テストでis_file()
メソッドを呼び出すpathlib.Path
インスタンスオブジェクトをMagicMock
に置き換え、置き換えたMockのis_file()
メソッドをtargetに指定してpatchを適用します。
オブジェクトを対象とするため、mocker.patch.object()
を使用します。
from sample.foo import Foo
def test_has_file(mocker):
foo = Foo()
# foo._work_fileをMagicMockに置き換え、is_file()にpatchを適用
foo._work_file = mocker.MagicMock()
mocker.patch.object(
foo._work_file,
"is_file",
return_value=True)
assert foo.has_work_file() == True
プロパティ
pathlib.Path
クラスのプロパティである、name
にpatchを適用してテストを実施します。
from pathlib import Path
class Foo:
def __init__(self):
self._work_directory = Path("./work")
self._work_file = self._work_directory / Path("work_file")
def get_work_file_name(self):
return self._work_file.name
インスタンスオブジェクトのプロパティをPropertyMockに置き換え
テストでname
プロパティを呼び出すpathlib.Path
インスタンスオブジェクトをMagicMock
に置き換え、置き換えたMockのname
プロパティにPropertyMockを設定します。
from sample.foo import Foo
def test_get_work_file_name(mocker):
foo = Foo()
# foo._work_fileをMagicMockに置き換え
foo._work_file = mocker.MagicMock()
# foo._work_fileのnameプロパティにPropertyMockを設定
type(foo._work_file).name = mocker.PropertyMock(return_value="mocked_value")
assert foo.get_work_file_name() == "mocked_value"
インスタンス生成操作
インスタンスオブジェクトをテスト対象メソッド内で生成している場合は、代入によりpatchを適用するインスタンスオブジェクトをMockに置き換えることができません。
この場合は、インスタンス生成操作に対してMockを返却するようにpatchを適用します。
patchはクラスオブジェクトに対して適用されるため、テストメソッドを終了するまでは、以降に生成するすべてのインスタンスはMockとなります。
テスト対象メソッド
from pathlib import Path
class Foo:
@staticmethod
def is_file_exist(path):
# pathlib.Pathクラスのインスタンスオブジェクトを生成し、is_file()を呼び出し
return Path(path).is_file()
インスタンス生成操作を指定してpatchを適用
pathlib.Path
クラスのインスタンス生成操作をtargetに指定してpatchを適用します。
インスタンス生成操作を対象とするため、mocker.patch()
を使用します。
from sample.foo import Foo
def test_is_file_exist(mocker):
# Path.is_file()のpatchを適用したMagicMockを作成
mocked_path = mocker.MagicMock()
mocker.patch.object(mocked_path, "is_file", return_value=True)
# sample.fooモジュールにimportされているPathインスタンスオブジェクトの生成操作にpatchを適用
# インスタンス生成操作はPathクラスのメソッドではないため、mocker.patch.object() では適用できない
mocker.patch("sample.foo.Path", return_value=mocked_path)
assert Foo.is_file_exist("/path/to/file/file_name") == True
参考
-
patchを適用するクラスが
__slots__
を定義している場合は、patchを適用するメソッド(属性)がread onlyとなるため、patchを適用できない。 ↩