はじめに
この記事では、Pythonの unittest のpatch
の詳細な使い方について紹介します。
patch
Pythonのunittestのpatch()
メソッドは、モックオブジェクトを生成して、指定されたパッチ先オブジェクトの参照を入れ替えます。主要な引数は次のとおりです。
引数 | 説明 |
---|---|
target |
パッチするオブジェクト。文字列形式でモジュール名を指定するか、オブジェクト自体を指定する。 |
new |
target を置き換えるオブジェクト。省略可能。 |
side_effect |
モックが呼び出されたときに発生させる例外、返す値、または呼び出し可能オブジェクトを定義する関数。省略可能。 |
return_value |
モックの呼び出し時に返される値。省略可能。 |
autospec |
True の場合、new オブジェクトがtarget オブジェクトのスペックをまさしく満たしていることを保証する。 |
spec |
モックのスペックオブジェクトを指定する。省略可能。 |
create |
モックの呼び出しに対して自動的に属性を作成するかどうかを指定するブール値。省略可能で、デフォルトはFalse 。 |
new_callable |
new が呼び出し可能なオブジェクトでない場合、呼び出し可能なオブジェクトを作成するための呼び出し可能オブジェクトを指定する。 |
unsafe |
パッチング対象がモジュールの場合、False を指定すると検証が行われます。省略可能で、デフォルトはTrue 。 |
new
patch
メソッドのnew
引数を使用すると、元のオブジェクトを完全に置き換えることができます。この引数を使用すると、モックオブジェクトとして完全な新しいオブジェクトを作成できます。以下は、new
引数を使用してFoo
オブジェクトを置き換える例です。
from unittest.mock import patch
class Foo:
def bar(self):
return "original"
with patch('__main__.Foo', new=lambda: "mock"):
f = Foo()
assert f.bar() == "mock"
この例では、Foo
クラスのインスタンスに対してbar()
メソッドを呼び出したときに"mock"
が返されます。つまり、元のオブジェクトがモックオブジェクトに置き換えられます。
グローバル変数にダミーの値をセットすることも可能です。
from unittest.mock import patch
def test_my_global_variable():
with patch('my_module.my_global_variable', new='dummy'):
assert my_module.my_global_variable == 'dummy'
この例では、my_module.my_global_variable
を文字列'dummy'
に置き換えています。new
引数は、グローバル変数に対して、与えられた新しい値をセットします。
return_value
return_value
引数を使用すると、モックが呼び出されたときに返される値を指定できます。次の例では、my_function
が呼び出されたときにreturn_value
で指定した値を返すモックを作成しています。
from unittest.mock import patch
def my_function():
return 42
with patch('__main__.my_function', return_value=84):
result = my_function()
print(result) # 84
wraps
wraps
引数を使用すると、モックがラップするオブジェクトの振る舞いを模倣できます。つまり、ラップするオブジェクトが持つ属性やメソッドを、モックも持つようになります。次の例では、my_function
が呼び出されたときに、本来のmy_function
を実行するモックを作成しています。
from unittest.mock import patch
def my_function():
return 42
with patch('__main__.my_function', wraps=my_function):
result = my_function()
print(result) # 42
side_effect
side_effect
引数を使用すると、モックが呼び出されたときに発生する例外を指定できます。次の例では、my_function
が呼び出されたときにValueError
を発生させるモックを作成しています。
from unittest.mock import patch
def my_function():
return 42
with patch('__main__.my_function', side_effect=ValueError('oops')):
result = my_function()
print(result) # ValueError: oops
patch.object
patch.object()
は、指定されたオブジェクトの属性をモックオブジェクトに置き換えるために使用されるunittest.mock
のメソッドです。このメソッドを使用すると、クラスのインスタンス属性やグローバル変数など、モジュール内の任意のオブジェクトの属性を置き換えることができます。これにより、テストが特定の状態で実行されるように設定できます。
patch.object()
の構文は次のとおりです。
patch.object(target, attribute, new=DEFAULT, **kwargs)
引数 | 説明 |
---|---|
target |
パッチするオブジェクトを指定する。 |
attribute |
パッチする属性の名前を指定する。 |
new |
オブジェクトの新しい値を指定する。デフォルト値はDEFAULT であり、これによりモックオブジェクトが自動的に作成される。 |
**kwargs |
追加のパッチング引数を指定するために使用される。 |
以下は、patch.object()
を使用した例です。
from unittest.mock import patch
class MyClass:
def __init__(self):
self.x = 10
def my_function():
obj = MyClass()
return obj.x
with patch.object(MyClass, 'x', 20):
assert my_function() == 20
この例では、MyClass
のインスタンス属性x
をパッチして、20
に設定しています。my_function()
がMyClass
のインスタンスを作成し、x
を返すため、 my_function()
の戻り値は20
になります。
patch.object
の他の例としては次のようなものがあります。
- オブジェクトのメソッドを置き換える
from unittest.mock import patch
class MyClass:
def my_method(self):
return "original"
with patch.object(MyClass, "my_method", return_value="mocked"):
obj = MyClass()
result = obj.my_method()
print(result) # => "mocked"
- モジュールの属性を置き換える
from unittest.mock import patch
import my_module
with patch.object(my_module, "my_function", return_value="mocked"):
result = my_module.my_function()
print(result) # => "mocked"
- 例外を発生させる
from unittest.mock import patch
def my_function():
raise ValueError("original")
with patch.object(__main__, "my_function", side_effect=RuntimeError("mocked")):
with pytest.raises(RuntimeError, match="mocked"):
my_function()
patch との違い
patch
とpatch.object
は、両方ともモックの作成と設定を行うためのunittest.mock
のメソッドですが、使用方法に違いがあります。
patch
は、モック対象のオブジェクトを指定する文字列形式のパスまたはオブジェクトを引数に取ります。モック対象のオブジェクトがクラスメソッドである場合、第1引数に渡された文字列に含まれるクラス名をモックし、モックオブジェクトに置き換えます。また、patch
は、with
ステートメント内で使用することを前提としています。
一方、patch.object
は、モック対象のオブジェクトと、そのオブジェクトの属性を指定する文字列を引数に取ります。モック対象のオブジェクトがクラスメソッドである場合でも、第1引数に渡されたオブジェクト自体がモックされ、属性の置き換えに使用されます。patch.object
は、with
ステートメント内で使用する必要はありません。
したがって、patch.object
を使用すると、オブジェクトの特定の属性のみをモックすることができますが、patch
を使用すると、オブジェクト全体をモックすることができます。
例えば、次のようなコードがあるとします。
class MyClass:
def my_method(self):
return 'real'
my_instance = MyClass()
次のようにpatch
を使用して、MyClass
全体をモックすることができます。
from unittest.mock import patch
with patch('my_module.MyClass') as MockClass:
instance = MockClass.return_value
instance.my_method.return_value = 'mocked'
assert my_instance.my_method() == 'mocked'
一方、次のようにpatch.object
を使用して、MyClass
のmy_method
属性のみをモックすることができます。
from unittest.mock import patch
with patch.object(my_instance, 'my_method') as mock_method:
mock_method.return_value = 'mocked'
assert my_instance.my_method() == 'mocked'
デコレータと with 文
Pythonのunittestのpatch
において、デコレータとwith
文を使用することは、基本的に同じ効果がありますが、書き方に違いがあります。
デコレータを使用する場合は、テストケース内の全てのテストメソッドでモックを有効にすることができます。以下は、デコレータを使用した例です。
import unittest
from unittest.mock import patch
class MyTestCase(unittest.TestCase):
@patch('mymodule.some_function')
def test_my_function(self, mock_some_function):
# test ode with mocked function
with
文を使用する場合は、with
文内だけでモックを有効にすることができます。以下は、with
文を使用した例です。
import unittest
from unittest.mock import patch
class MyTestCase(unittest.TestCase):
def test_my_function(self):
with patch('mymodule.some_function') as mock_some_function:
# test code with mocked function
一般的に、デコレータを使用すると、コードが簡潔になります。しかし、テストメソッドごとにモックをカスタマイズする必要がある場合には、with
文を使用することができます。また、with
文を使用する場合は、with
ブロックの終了時に自動的にモックがクリーンアップされるため、テスト中に何かが失敗した場合でもクリーンアップが行われます。
複数の patch を定義
複数のpatchを定義する例を以下に示します。
例えば、次のようなモジュールがあるとします。
import os
def my_function():
return os.path.abspath(__file__)
このモジュールに対して、複数のパッチを定義する場合、次のようにします。
import unittest.mock
import my_module
class TestMyModule(unittest.TestCase):
def test_my_function(self):
with unittest.mock.patch('my_module.os'):
with unittest.mock.patch('my_module.os.path.abspath') as abspath_mock:
abspath_mock.return_value = 'my/absolute/path'
result = my_module.my_function()
self.assertEqual(result, 'my/absolute/path')
この例では、os
モジュールとそのpath.abspath
関数に対してパッチを適用しています。パッチを適用する際には、unittest.mock.patch()
メソッドを使用し、引数にパッチを適用したいオブジェクトのパスを指定します。パッチを複数適用する場合は、入れ子のwith
文を使用します。また、パッチオブジェクトはas
句を使ってエイリアスをつけることができます。ここではabspath_mock
というエイリアスをつけ、return_value
属性を使用してモックの戻り値を設定しています。
複数のpatch
デコレータを定義する場合は、次のようにします。
from unittest.mock import patch
@patch('module1.function1')
@patch('module2.function2')
def test_my_function(mock_function2, mock_function1):
# test code
pass
この例では、module1.function1
とmodule2.function2
をパッチするために、2つのpatch
デコレータを使用しています。この場合、テスト関数の引数の順序は、デコレータの順序と一致するように注意してください。つまり、mock_function1
はmodule1.function1
のモックであり、mock_function2
はmodule2.function2
のモックです。
参考