Traffine I/O

日本語

2023-02-17

unittestのmock

unittest の mock とは

Pythonのunittestライブラリを使ってテストを書く場合、モックを使うことで依存関係のないテストを書くことができます。モックを使うと、外部のリソースや他のモジュールとのやりとりを模擬することができ、テストの信頼性や速度を向上させることができます。

以下は、Pythonのunittestライブラリでモックを使ったテストを書くための基本的な手順です。

  1. unittest.mockモジュールをインポートする
python
from unittest.mock import MagicMock, Mock, patch
  1. テスト対象のモジュール、関数、クラスをインポートする
python
from my_module import my_function
  1. テストクラスを作成し、テストメソッドを定義する
python
import unittest

class TestMyFunction(unittest.TestCase):
    def test_my_function(self):
        # write test code here
  1. テストメソッド内で、モックオブジェクトを作成し、テスト対象の関数やメソッドを置き換える
python
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クラスは単一の属性またはメソッドをモック化するオブジェクトです。属性やメソッドに対して、任意の戻り値を設定することができます。

python
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オブジェクトを作成することができます。

python
from unittest.mock import MagicMock

magic_mock_obj = MagicMock()

このオブジェクトに対して、任意の属性を設定することができます。

python
magic_mock_obj.some_attribute = 10

この場合、 some_attribute属性には値10が設定されます。また、属性に対してアサーションを行うこともできます。

python
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において、MockMagicMockは共にモックオブジェクトを生成するためのツールですが、微妙な違いがあります。

Mockオブジェクトは、呼び出し可能な関数やクラスをモックするために使用されます。Mockオブジェクトは、モックされたオブジェクトに対するアサーションや、呼び出し時に渡される引数の検証などを行うことができます。

一方、MagicMockはMockオブジェクトのサブクラスであり、属性へのアクセスもモックすることができます。具体的には、属性を設定したり、メソッドを呼び出した際に自動的にMockオブジェクトを生成したりすることができます。

つまり、Mockオブジェクトは呼び出し可能なオブジェクトのみをモックすることができるのに対して、MagicMockは呼び出し可能なオブジェクトだけでなく、属性もモックすることができます。

例えば、以下のようなクラスがあった場合、Mockオブジェクトではget_nameメソッドのモック化しかできませんが、MagicMockではget_nameメソッドだけでなく、name属性のモック化もできます。

python
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リクエストが実際には行われないようにします。

python
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_getreturn_valueに設定しています。これにより、requests.get()関数が呼び出された場合にmock_responseが返されます。

最後に、requests.get()を呼び出し、レスポンスのテキストと、モックオブジェクトのcalled属性がTrueであることを検証しています。

Mock オブジェクトの属性とメソッド

Mockオブジェクトには様々な属性やメソッドがあります。以下は、Mockオブジェクトが持つ主要な属性やメソッドです。

return_value

Mockオブジェクトのreturn_value属性は、モックが呼び出された際に返す値を設定したり、取得したりするために使用されます。

例えば、以下のようなmockがあったとします。

python
from unittest.mock import Mock

mock = Mock()

このmockを呼び出した際に返す値を設定するには、return_value属性を使用します。

python
mock.return_value = 10

この場合、モックが呼び出された際には、常に10が返されます。

python
assert mock() == 10

また、return_value属性を使用して、モックが呼び出された際に返される値を取得することもできます。

python
assert mock.return_value == 10

return_value属性は、モックが呼び出された際に返される値を制御するために使用されます。テストで使用するときは、期待通りの値が返されるかどうかを確認するために使用することができます。また、モックが呼び出された際に返す値を設定することで、プログラムの特定の振る舞いをテストすることができます。

side_effect

Mockオブジェクトのside_effect属性は、モックが呼び出された際に実行される関数を設定するために使用されます。

以下は、side_effectを使用して例外をスローするmockの例です。

python
from unittest.mock import Mock

def func():
    raise ValueError("Error")

mock = Mock(side_effect=func)

このモックを呼び出すと、指定した関数が実行されます。

python
mock()
# ValueError: Error

side_effectは、呼び出しの結果として返すものが単純な値でない場合、または複数回の呼び出しに対して異なる結果を返したい場合に便利です。例えば、以下のようにリストを返す関数を持つモックがあったとします。

python
mock = Mock()
mock.side_effect = [[1, 2], [3, 4], [5, 6]]

このモックは、1回目に呼び出された際には[1, 2]を返し、2回目に呼び出された際には[3, 4]を返します。3回目以降に呼び出された場合は、常に[5, 6]を返します。

python
assert mock() == [1, 2]
assert mock() == [3, 4]
assert mock() == [5, 6]
assert mock() == [5, 6]

side_effectは、モックが呼び出された際に実行される関数を設定するために使用されます。テストで使用するときは、関数が期待どおりに実行されるかどうかを確認するために使用することができます。

called

Mockオブジェクトのcalled属性は、そのモックが呼び出されたかどうかを示す真偽値です。モックが呼び出された場合はTrue、呼び出されていない場合はFalseとなります。

例えば、以下のようなモックがあったとします。

python
from unittest.mock import Mock

mock = Mock()

このモックが呼び出される前の状態では、called属性の値はFalseとなります。

python
assert mock.called == False

このモックを呼び出すとcalled属性の値はTrueになります。

python
mock()
assert mock.called == True

called属性は、モックが呼び出されたかどうかを検証するために使用することができます。例えば、モックが呼び出されるのを確認した後に何らかの処理を行う必要がある場合などに有用です。

call_args

Mockオブジェクトのcall_args属性は、モックが最後に呼び出された際の引数を格納するオブジェクトです。このオブジェクトは、unittest.mock.callオブジェクトの形式で返されます。

例えば、以下のようなmockがあったとします。

python
from unittest.mock import Mock

mock = Mock()
mock(1, 2, 3)

この場合、call_args属性を使用すると、モックが最後に呼び出された際の引数を取得することができます。

python
assert mock.call_args == ((1, 2, 3),)

このように、call_args属性を使用すると、最後に呼び出された引数を取得し、テストで検証することができます。また、call_args属性を使用することで、モックがどのように呼び出されたかを確認することもできます。

call_args属性は、assert_called_with()などの便利なアサーションメソッドと組み合わせて使用することができます。また、モックが期待通りに引数を受け取ったかどうかを確認するために使用することができます。

call_args_list

Mockオブジェクトのcall_args_list属性は、そのモックが呼び出された際の全ての引数を表すタプルのリストです。これは、モックが呼び出された際に渡された全ての引数を記録するために使用することができます。

例えば、以下のようなmockがあったとします。

python
from unittest.mock import Mock

mock = Mock()

このモックを2回呼び出して、それぞれ異なる引数を渡すと、call_args_list属性にはそれぞれの引数が格納されたタプルが含まれます。

python
mock('foo')
mock('bar')

assert mock.call_args_list == [('foo',), ('bar',)]

call_args_list属性を使用すると、モックが呼び出されたときに渡された全ての引数を取得し、テストで検証することができます。また、引数の順序や数が変更された場合にも、テストが失敗することを防ぐことができます。

注意点としては、call_args_list属性には、モックが呼び出された順序で引数が格納されるため、異なる順序で呼び出された場合には異なる結果が得られることです。

call_count

Mockオブジェクトのcall_count属性は、そのモックが呼び出された回数を返す整数値です。つまり、モックがいくつ呼び出されたかを確認するために使用されます。

例えば、以下のようなモックがあったとします。

python
from unittest.mock import Mock

mock = Mock()

このモックを3回呼び出すと、call_count属性には呼び出し回数である3が格納されます。

python
mock()
mock()
mock()

assert mock.call_count == 3

call_count属性を使用すると、モックが呼び出された回数を取得し、テストで検証することができます。例えば、モックが期待された回数だけ呼び出されたかどうかを確認するために使用することができます。

call_count属性は、assert_called_once()などの便利なアサーションメソッドと組み合わせて使用することができます。また、呼び出し回数を監視することで、プログラムの正常な動作やエラーの発生を確認することもできます。

mock_calls

Mockオブジェクトのmock_calls属性は、そのモックが呼び出された際の全ての呼び出しを表すメソッド呼び出しのリストです。つまり、モックが呼び出されたときのメソッド名と引数、戻り値などの情報を記録するために使用されます。

例えば、以下のようなモックがあったとします。

python
from unittest.mock import Mock

mock = Mock()

このモックを2回呼び出して、それぞれ異なる引数を渡すと、mock_calls属性にはそれぞれの呼び出しに関する情報が格納されたオブジェクトが含まれます。

python
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オブジェクトがあるとします。

python
from unittest.mock import Mock

mock = Mock(return_value=42)
mock(1, 2, 3)

このMockオブジェクトは、引数1, 2, 3で1回呼び出され、戻り値として42を返します。reset_mockを呼び出すと、これらの属性がリセットされます。

python
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オブジェクトがあるとします。

python
from unittest.mock import Mock

mock_obj = Mock()

このMockオブジェクトが1回以上呼び出されたことをアサートするには、次のようにします。

python
# 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オブジェクトを指定することができます。

このメソッドは、次のように使用できます。

python
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_objcall(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_calledassert_called_withを呼び出すこともできます。

python
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を使った例です。

python
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関数をモックする例です。

python
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メソッドをモックする例です。

python
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を設定しています。

モックオブジェクトを利用した例外のテスト

以下は、モックオブジェクトを利用して例外の発生をテストする例です。

python
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()関数が呼び出された場合に返されるレスポンスをモック化しています。

python
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.getMagicMockに置き換え、return_valueresponse_mockに設定しています。最後に、requests.get('https://www.example.com')を呼び出し、レスポンスのステータスコードとJSONの値を検証しています。

patch の例

patchを使用した例をいくつか紹介します。

外部モジュールの関数をモックする

例えば、以下のようなmathモジュールを使う関数があるとします。

python
import math

def calculate_square_root(num):
    return math.sqrt(num)

このcalculate_square_root関数の中で、math.sqrt関数をモックすることができます。以下は、patchを使用してmath.sqrtをモックする例です。

python
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を設定して、モックの返り値を設定しています。

オブジェクトの属性をモックする

以下のようなクラスがあるとします。

python
class MyClass:
    def __init__(self):
        self.my_attribute = 42

    def my_method(self):
        return self.my_attribute

このクラスのインスタンスのmy_attribute属性をモックすることができます。以下は、patchを使用してmy_attribute属性をモックする例です。

python
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属性をモックしています。

ファイルのオープンをモックする

以下は、ファイルを読み込む関数の例です。

python
def read_file(filename):
    with open(filename, 'r') as f:
        return f.read()

この関数をテストするために、ファイルが存在しない場合をテストするために、モックファイルオブジェクトを使用することができます。これを行うには、patch関数を使用してファイルを置き換え、モックファイルオブジェクトを返すように設定します。

python
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コンテキスト内で、モックファイルオブジェクトのメソッドを置き換えることができます。

参考

https://docs.python.org/3/library/unittest.mock.html

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!