unittest の mock とは
Pythonのunittestライブラリを使ってテストを書く場合、モックを使うことで依存関係のないテストを書くことができます。モックを使うと、外部のリソースや他のモジュールとのやりとりを模擬することができ、テストの信頼性や速度を向上させることができます。
以下は、Pythonのunittestライブラリでモックを使ったテストを書くための基本的な手順です。
unittest.mock
モジュールをインポートする
from unittest.mock import MagicMock, Mock, patch
- テスト対象のモジュール、関数、クラスをインポートする
from my_module import my_function
- テストクラスを作成し、テストメソッドを定義する
import unittest
class TestMyFunction(unittest.TestCase):
def test_my_function(self):
# write test code here
- テストメソッド内で、モックオブジェクトを作成し、テスト対象の関数やメソッドを置き換える
import unittest
from unittest.mock import MagicMock
class TestMyFunction(unittest.TestCase):
def test_my_function(self):
mock_obj = MagicMock(return_value=42)
with patch('my_module.my_function', mock_obj):
result = my_function()
self.assertEqual(result, 42)
この例では、my_module.my_function()
が呼び出された場合、mock_obj
が呼び出され、return_value
で指定した値が返されます。テストでは、my_function()
の戻り値が42であることを確認しています。
モックとパッチ
Pythonのunittestのモックは、テスト中に本物のオブジェクトの代わりに使用されるオブジェクトで、コード内で他のオブジェクトに依存している場合でも、テスト中に必要な振る舞いをシミュレートすることができます。これにより、テスト中に実際のデータベースやネットワークなどの外部リソースにアクセスする必要がなくなります。Mockオブジェクトを使用することで、テストの速度が向上し、テストの信頼性が向上します。
Pythonのunittestのパッチは、モックを使用してテスト中に実行されるコードを変更することを可能にする機能です。パッチは、テスト中に実際のオブジェクトを置き換え、必要な振る舞いをシミュレートするために使用されます。パッチは、単一のテスト中で使用することができますし、setUp
メソッドで設定することで、全てのテストで共有されることもあります。例えば、外部APIを呼び出すメソッドをテストする場合、APIが実際に呼び出されると、テストの実行時間が増加し、APIがオフラインである場合にテストが失敗する可能性があります。そこで、パッチを使用してAPI呼び出しをモックし、テスト中にAPIから取得したデータをシミュレートすることができます。これにより、テストの速度が向上し、APIがオフラインであってもテストが失敗することはありません。
用語 | 説明 |
---|---|
モック | 引数や呼び出された回数をチェックする機能を持ち、返り値や例外送出も自在に設定可能なオブジェクト |
パッチ | システムの関数やクラスを Mock オブジェクトに置き換えること |
unittest.mock の主要なクラスと関数
unittest.mock
パッケージには、次のような主要なクラスと関数があります。
Mock
: ほかのオブジェクトを置き換えるために使用されるモックオブジェクトMagicMock
: マジックメソッドを持つMockクラスのサブクラスpatch
: テストで使用するオブジェクトを置き換えるためのコンテキストマネージャー
これらのクラスと関数を使用すると、テスト中に外部のオブジェクトを簡単にモック化することができます。例えば、ファイルを読み込むために使用する関数がある場合、ファイルが存在しない場合の処理をテストするために、モックファイルオブジェクトを使用することができます。
Mock
Pythonのunittestモジュールには、ユニットテストのためにモックオブジェクトを作成するためのMock
クラスがあります。Mock
クラスは単一の属性またはメソッドをモック化するオブジェクトです。属性やメソッドに対して、任意の戻り値を設定することができます。
from unittest.mock import Mock
# create mock object
mock_obj = Mock()
# set attribute
mock_obj.attr = 'value'
# assert attribute
assert mock_obj.attr == 'value'
# set method
mock_obj.method.return_value = 42
# call method
result = mock_obj.method()
# verify result
assert result == 42
MagicMock
MagicMock
は、Pythonのunittest.mock
モジュールに含まれるクラスで、 Mock
クラスのサブクラスです。 MagicMock
は、 Mock
クラスの全ての機能に加えて、属性やメソッドを自動的に生成する能力を持っています。
MagicMock
を使用すると、オブジェクトの属性を呼び出した場合に自動的に別のMagicMock
オブジェクトが返されるため、テストコードを記述する際に手間が省けます。また、属性に設定された値は保存されるため、後でアクセスできます。
例えば、次のようなMagicMock
オブジェクトを作成することができます。
from unittest.mock import MagicMock
magic_mock_obj = MagicMock()
このオブジェクトに対して、任意の属性を設定することができます。
magic_mock_obj.some_attribute = 10
この場合、 some_attribute
属性には値10が設定されます。また、属性に対してアサーションを行うこともできます。
assert magic_mock_obj.some_attribute == 10
MagicMock
は、属性アクセス時に自動的にMagicMock
オブジェクトを生成します。例えば、 magic_mock_obj.some_object.some_method()
というコードがある場合、 some_object
という属性が存在しない場合でもエラーが発生せず、自動的にMagicMock
オブジェクトが生成されます。そのため、テストコードを記述する際に手間が省けます。
また、MagicMock
オブジェクトにはMock
オブジェクトと同じように、呼び出しに関する情報を追跡する機能があります。例えば、 MagicMock
オブジェクトが呼び出された回数や引数などを追跡することができます。
Mock との違い
Pythonのunittestにおいて、Mock
とMagicMock
は共にモックオブジェクトを生成するためのツールですが、微妙な違いがあります。
Mockオブジェクトは、呼び出し可能な関数やクラスをモックするために使用されます。Mockオブジェクトは、モックされたオブジェクトに対するアサーションや、呼び出し時に渡される引数の検証などを行うことができます。
一方、MagicMockはMockオブジェクトのサブクラスであり、属性へのアクセスもモックすることができます。具体的には、属性を設定したり、メソッドを呼び出した際に自動的にMockオブジェクトを生成したりすることができます。
つまり、Mockオブジェクトは呼び出し可能なオブジェクトのみをモックすることができるのに対して、MagicMockは呼び出し可能なオブジェクトだけでなく、属性もモックすることができます。
例えば、次のようなクラスがあった場合、Mockオブジェクトではget_name
メソッドのモック化しかできませんが、MagicMockではget_name
メソッドだけでなく、name
属性のモック化もできます。
class Person:
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
したがって、属性のモック化が必要な場合には、MagicMockを使用することが適切です。
patch
unittest.mock
モジュールには、モジュール、クラス、オブジェクト、または関数のパッチングを行うために使用されるpatch()
関数があります。patch()
関数は、モック化されたオブジェクトを返し、パッチを適用したコンテキスト内でのみ使用できます。
patch()
関数は、デコレータとして使用することもできます。この場合、デコレートされた関数の引数として、パッチされたオブジェクトが渡されます。
以下は、patch()
関数の例です。requests
モジュールのget()
関数をモック化し、HTTPリクエストが実際には行われないようにします。
from unittest.mock import patch
import requests
@patch('requests.get')
def test_requests(mock_get):
mock_response = 'Mock Response'
mock_get.return_value.text = mock_response
response = requests.get('https://www.example.com')
assert response.text == mock_response
assert mock_get.called
この例では、requests.get
をパッチしています。patch()
関数には、パッチ対象のオブジェクトの名前またはパスを引数として渡します。この例では、 requests.get
をパッチするために、文字列形式のパスrequests.get
を使用しています。
test_requests()
関数内では、モックオブジェクトmock_get
を作成し、text
属性をmock_response
に設定しています。次に、mock_get
をreturn_value
に設定しています。これにより、requests.get()
関数が呼び出された場合にmock_response
が返されます。
最後に、requests.get()
を呼び出し、レスポンスのテキストと、モックオブジェクトのcalled
属性がTrue
であることを検証しています。
Mock オブジェクトの属性とメソッド
Mockオブジェクトには様々な属性やメソッドがあります。以下は、Mockオブジェクトが持つ主要な属性やメソッドです。
return_value
Mockオブジェクトのreturn_value
属性は、モックが呼び出された際に返す値を設定したり、取得したりするために使用されます。
例えば、次のようなmockがあったとします。
from unittest.mock import Mock
mock = Mock()
このmockを呼び出した際に返す値を設定するには、return_value
属性を使用します。
mock.return_value = 10
この場合、モックが呼び出された際には、常に10が返されます。
assert mock() == 10
また、return_value
属性を使用して、モックが呼び出された際に返される値を取得することもできます。
assert mock.return_value == 10
return_value
属性は、モックが呼び出された際に返される値を制御するために使用されます。テストで使用するときは、期待通りの値が返されるかどうかを確認するために使用することができます。また、モックが呼び出された際に返す値を設定することで、プログラムの特定の振る舞いをテストすることができます。
side_effect
Mockオブジェクトのside_effect
属性は、モックが呼び出された際に実行される関数を設定するために使用されます。
以下は、side_effect
を使用して例外をスローするmockの例です。
from unittest.mock import Mock
def func():
raise ValueError("Error")
mock = Mock(side_effect=func)
このモックを呼び出すと、指定した関数が実行されます。
mock()
# ValueError: Error
side_effect
は、呼び出しの結果として返すものが単純な値でない場合、または複数回の呼び出しに対して異なる結果を返したい場合に便利です。例えば、次のようにリストを返す関数を持つモックがあったとします。
mock = Mock()
mock.side_effect = [[1, 2], [3, 4], [5, 6]]
このモックは、1回目に呼び出された際には[1, 2]
を返し、2回目に呼び出された際には[3, 4]
を返します。3回目以降に呼び出された場合は、常に[5, 6]
を返します。
assert mock() == [1, 2]
assert mock() == [3, 4]
assert mock() == [5, 6]
assert mock() == [5, 6]
side_effect
は、モックが呼び出された際に実行される関数を設定するために使用されます。テストで使用するときは、関数が期待どおりに実行されるかどうかを確認するために使用することができます。
called
Mockオブジェクトのcalled
属性は、そのモックが呼び出されたかどうかを示す真偽値です。モックが呼び出された場合はTrue
、呼び出されていない場合はFalse
となります。
例えば、次のようなモックがあったとします。
from unittest.mock import Mock
mock = Mock()
このモックが呼び出される前の状態では、called
属性の値はFalse
となります。
assert mock.called == False
このモックを呼び出すとcalled
属性の値はTrue
になります。
mock()
assert mock.called == True
called
属性は、モックが呼び出されたかどうかを検証するために使用することができます。例えば、モックが呼び出されるのを確認した後に何らかの処理を行う必要がある場合などに有用です。
call_args
Mockオブジェクトのcall_args
属性は、モックが最後に呼び出された際の引数を格納するオブジェクトです。このオブジェクトは、unittest.mock.call
オブジェクトの形式で返されます。
例えば、次のようなmockがあったとします。
from unittest.mock import Mock
mock = Mock()
mock(1, 2, 3)
この場合、call_args
属性を使用すると、モックが最後に呼び出された際の引数を取得することができます。
assert mock.call_args == ((1, 2, 3),)
このように、call_args
属性を使用すると、最後に呼び出された引数を取得し、テストで検証することができます。また、call_args
属性を使用することで、モックがどのように呼び出されたかを確認することもできます。
call_args
属性は、assert_called_with()
などの便利なアサーションメソッドと組み合わせて使用することができます。また、モックが期待通りに引数を受け取ったかどうかを確認するために使用することができます。
call_args_list
Mockオブジェクトのcall_args_list
属性は、そのモックが呼び出された際の全ての引数を表すタプルのリストです。これは、モックが呼び出された際に渡された全ての引数を記録するために使用することができます。
例えば、次のようなmockがあったとします。
from unittest.mock import Mock
mock = Mock()
このモックを2回呼び出して、それぞれ異なる引数を渡すと、call_args_list
属性にはそれぞれの引数が格納されたタプルが含まれます。
mock('foo')
mock('bar')
assert mock.call_args_list == [('foo',), ('bar',)]
call_args_list
属性を使用すると、モックが呼び出されたときに渡された全ての引数を取得し、テストで検証することができます。また、引数の順序や数が変更された場合にも、テストが失敗することを防ぐことができます。
注意点としては、call_args_list
属性には、モックが呼び出された順序で引数が格納されるため、異なる順序で呼び出された場合には異なる結果が得られることです。
call_count
Mockオブジェクトのcall_count
属性は、そのモックが呼び出された回数を返す整数値です。つまり、モックがいくつ呼び出されたかを確認するために使用されます。
例えば、次のようなモックがあったとします。
from unittest.mock import Mock
mock = Mock()
このモックを3回呼び出すと、call_count
属性には呼び出し回数である3が格納されます。
mock()
mock()
mock()
assert mock.call_count == 3
call_count
属性を使用すると、モックが呼び出された回数を取得し、テストで検証することができます。例えば、モックが期待された回数だけ呼び出されたかどうかを確認するために使用することができます。
call_count
属性は、assert_called_once()
などの便利なアサーションメソッドと組み合わせて使用することができます。また、呼び出し回数を監視することで、プログラムの正常な動作やエラーの発生を確認することもできます。
mock_calls
Mockオブジェクトのmock_calls
属性は、そのモックが呼び出された際の全ての呼び出しを表すメソッド呼び出しのリストです。つまり、モックが呼び出されたときのメソッド名と引数、戻り値などの情報を記録するために使用されます。
例えば、次のようなモックがあったとします。
from unittest.mock import Mock
mock = Mock()
このモックを2回呼び出して、それぞれ異なる引数を渡すと、mock_calls
属性にはそれぞれの呼び出しに関する情報が格納されたオブジェクトが含まれます。
mock('foo')
mock('bar')
assert mock.mock_calls == [call('foo'), call('bar')]
mock_calls
属性を使用すると、モックが呼び出されたときの情報を取得し、テストで検証することができます。例えば、モックが期待された順序で呼び出されたかどうかを確認するために使用することができます。
mock_calls
属性には、呼び出されたメソッド名や引数、戻り値などの情報が含まれるため、テストのデバッグや検証に非常に役立ちます。ただし、この属性は大量の情報を含む場合があるため、適度な数の呼び出しに限定することが望ましい場合もあります。
reset_mock
reset_mock
メソッドは、Mockオブジェクトが以前に呼び出されたかどうか、呼び出しの引数や戻り値など、過去の状態をリセットするために使用されます。これは、複数のテスト間で同じMockオブジェクトを使用する場合に特に有用です。
reset_mock
を呼び出すと、次の属性がリセットされます。
call_args
call_count
call_args_list
mock_calls
return_value
side_effect
例えば、次のようなMockオブジェクトがあるとします。
from unittest.mock import Mock
mock = Mock(return_value=42)
mock(1, 2, 3)
このMockオブジェクトは、引数1, 2, 3で1回呼び出され、戻り値として42を返します。reset_mock
を呼び出すと、これらの属性がリセットされます。
mock.reset_mock()
assert mock.call_count == 0
assert mock.call_args is None
assert mock.call_args_list == []
assert mock.mock_calls == []
assert mock.return_value is None
assert mock.side_effect is None
このように、reset_mock
を使用することで、Mockオブジェクトをテスト間で再利用することができます。
assert_called
assert_called()
メソッドは、Mockオブジェクトが少なくとも1回呼び出されたことをアサートするために使用されます。このメソッドは、Mockオブジェクトが通常の関数やメソッドのように呼び出された場合にのみ機能します。
例えば、次のようなMockオブジェクトがあるとします。
from unittest.mock import Mock
mock_obj = Mock()
このMockオブジェクトが1回以上呼び出されたことをアサートするには、次のようにします。
# assert that the mock object has been called at least once
mock_obj.assert_called()
このメソッドは、Mockオブジェクトが呼び出されなかった場合には例外を発生させます。したがって、呼び出しをアサートする前にMockオブジェクトを少なくとも1回呼び出しておく必要があります。また、呼び出し回数が1回でなければならない場合は、assert_called_once()
メソッドを使用することもできます。
assert_has_calls
has_assert_calls()
メソッドは、Mockオブジェクトが期待された呼び出しを受け取ったかどうかをアサートするために使用されます。このメソッドには、期待される呼び出しを表す1つ以上のcall
オブジェクトを指定することができます。
このメソッドは、次のように使用できます。
from unittest.mock import Mock, call
mock_obj = Mock()
mock_obj(1, 2, 3)
mock_obj('a', 'b', 'c')
# asserts that the mock object has been called in the specified order
mock_obj.assert_has_calls([call(1, 2, 3), call('a', 'b', 'c')])
mock_obj.has_assert_calls([call(1, 2, 3), call('a', 'b', 'c')])
上記の例では、Mockオブジェクトmock_obj
がcall(1, 2, 3)
とcall('a', 'b', 'c')
の2つの呼び出しを受け取ったことをアサートしています。assert_has_calls()
メソッドは、呼び出しの順序が指定された順序であることも確認します。
assert_called_once
assert_called_once
は、Mockオブジェクトが1回だけ呼び出されたことを検証するメソッドです。assert_called_once
を呼び出す前に、Mockオブジェクトが1回だけ呼び出されたことを確認するために、assert_called
やassert_called_with
を呼び出すこともできます。
from unittest.mock import MagicMock
mock_obj = MagicMock()
mock_obj()
mock_obj.assert_called_once()
assert_called_with
assert_called_with
は、Mockオブジェクトが指定された引数で呼び出されたことを検証するメソッドです。呼び出された引数が期待値と一致している場合にのみ、テストがパスします。
以下はassert_called_with
を使った例です。
from unittest.mock import MagicMock
def test_example():
mock_obj = MagicMock()
mock_obj('foo', 'bar', baz='bazval')
mock_obj.assert_called_with('foo', 'bar', baz='bazval')
このテストでは、Mockオブジェクトを作成し、引数'foo'
と'bar'
、キーワード引数baz='bazval'
を使用して呼び出します。その後、assert_called_with
メソッドを使用して、mockが正しい引数で呼び出されたことを確認します。
このように、assert_called_with
メソッドを使用することで、モックが正しい引数で呼び出されていることを簡単に検証することができます。また、assert_called_with
メソッドを使用すると、呼び出し回数などの情報も同時に検証することができます。
Mock の例
Mock
を使った例をいくつか紹介します。
モジュールの関数をモックする
以下は、requests
モジュールのget
関数をモックする例です。
import requests
from unittest.mock import Mock
def test_fetch_data():
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"foo": "bar"}
requests.get = Mock(return_value=mock_response)
result = fetch_data()
assert result == {"foo": "bar"}
この例では、requests.get
関数をモックして、返り値としてMockオブジェクトを設定しています。Mockオブジェクトには、status_code
属性とjson() メソッドがあります。json()
メソッドは、return_value
属性によってMock
オブジェクトを返すように設定しています。
クラスのメソッドをモックする
以下は、Calculator
クラスのadd
メソッドをモックする例です。
from unittest.mock import Mock
class Calculator:
def add(self, x, y):
return x + y
def test_calculator():
# mock the get function in the requests module
calculator = Calculator()
# mock the add method of the Calculator class
calculator.add = Mock(return_value=10)
# run test
result = calculator.add(1, 2)
assert result == 10
この例では、Calculator
クラスのadd
メソッドをモックして、返り値として10
を設定しています。
モックオブジェクトを利用した例外のテスト
以下は、モックオブジェクトを利用して例外の発生をテストする例です。
from unittest.mock import Mock
def test_divide_by_zero():
mock_logger = Mock()
import logger
logger.error = mock_logger
try:
1 / 0
except ZeroDivisionError as e:
mock_logger.assert_called_with("division by zero")
この例では、logger
モジュールのerror
関数をモックして、ゼロ除算によって発生するZeroDivisionError
例外が、logger.error
関数が呼び出されることを確認するテストを行っています。
MagicMock の例
以下は、MagicMock
を使用した簡単な例です。例として、requests
モジュールのget()
関数を呼び出すコードがあります。この例では、get()
関数が呼び出された場合に返されるレスポンスをモック化しています。
import requests
from unittest.mock import MagicMock
def test_requests():
response_mock = MagicMock()
response_mock.status_code = 200
response_mock.json.return_value = {'key': 'value'}
requests.get = MagicMock(return_value=response_mock)
response = requests.get('https://www.example.com')
assert response.status_code == 200
assert response.json()['key'] == 'value'
この例では、response_mock
を作成し、status_code
属性を設定しています。また、json()
メソッドもモック化して、その戻り値を設定しています。次に、requests.get
をMagicMock
に置き換え、return_value
をresponse_mock
に設定しています。最後に、requests.get('https://www.example.com')
を呼び出し、レスポンスのステータスコードとJSONの値を検証しています。
patch の例
patch
を使用した例をいくつか紹介します。
外部モジュールの関数をモックする
例えば、次のようなmath
モジュールを使う関数があるとします。
import math
def calculate_square_root(num):
return math.sqrt(num)
このcalculate_square_root
関数の中で、math.sqrt
関数をモックすることができます。以下は、patch
を使用してmath.sqrt
をモックする例です。
import unittest
from unittest.mock import patch
from my_module import calculate_square_root
class TestCalculateSquareRoot(unittest.TestCase):
@patch('my_module.math.sqrt')
def test_calculate_square_root(self, mock_sqrt):
mock_sqrt.return_value = 2.0
result = calculate_square_root(4)
self.assertEqual(result, 2.0)
上記の例では、@patch('my_module.math.sqrt')
デコレータを使用して、math.sqrt
をモックしています。また、mock_sqrt.return_value
を設定して、モックの返り値を設定しています。
オブジェクトの属性をモックする
次のようなクラスがあるとします。
class MyClass:
def __init__(self):
self.my_attribute = 42
def my_method(self):
return self.my_attribute
このクラスのインスタンスのmy_attribute
属性をモックすることができます。以下は、patch
を使用してmy_attribute
属性をモックする例です。
import unittest
from unittest.mock import patch
from my_module import MyClass
class TestMyClass(unittest.TestCase):
@patch('my_module.MyClass.my_attribute', new=50)
def test_my_method(self):
my_instance = MyClass()
result = my_instance.my_method()
self.assertEqual(result, 50)
上記の例では、@patch('my_module.MyClass.my_attribute', new=50)
デコレータを使用して、my_attribute
属性をモックしています。
ファイルのオープンをモックする
以下は、ファイルを読み込む関数の例です。
def read_file(filename):
with open(filename, 'r') as f:
return f.read()
この関数をテストするために、ファイルが存在しない場合をテストするために、モックファイルオブジェクトを使用することができます。これを行うには、patch
関数を使用してファイルを置き換え、モックファイルオブジェクトを返すように設定します。
from unittest.mock import patch
def test_read_file():
with patch('builtins.open', return_value=Mock(spec=open)) as mock_file:
mock_file.return_value.__enter__.return_value.read.return_value = 'test file contents'
assert read_file('test.txt') == 'test file contents'
この例では、builtins.open
がモックオブジェクトに置き換えられます。mock_file.return_value
は、open
関数が返すオブジェクトを表します。モックファイルオブジェクトは、__enter__
メソッドでファイルオブジェクトを返し、read
メソッドでファイルの内容を返します。patch
コンテキスト内で、モックファイルオブジェクトのメソッドを置き換えることができます。
参考