13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【pytest】mocker.patch.objectを使ってみる

Last updated at Posted at 2019-07-12

環境

  • windows 10
  • python=3.7.3
  • pytest=5.0.1
  • pytest-mock=1.10.4

準備

pytestをインストールします。

$ pip install pytest

Mockを使うため、pytest-mockもインストールします。

$ pip install pytest-mock

テスト対象

以下のような、visaモジュールを使って外部の機器と通信を取るCommunicationクラスを例にとります。
Communicationクラスはopen_deviceメソッドを持っており、これのテストを考えます。

src/communication.py
import visa


class Communication(object):

    def __init__(self):
        self._rm = visa.ResourceManager()
        self._device = None

    def open_device(self, device_name: str) -> bool:
        try:
            self._device = self._rm.open_resource(device_name)
        except IOError:
            return False
        else:
            return True

テスト

open_deviceメソッド内で使われている、open_resource()はホストに対象機器が接続されているかどうかで挙動が変わってしまいます。なので、これをMockに置き換えます。

test/test_communication.py
import pytest

from src.communication import Communication


class TestCommunication(object):

    def test_open_device(self, mocker):
        com = Communication()
        rm_mock = mocker.MagicMock()
        rm_mock.open_resource = mocker.Mock()
        with mocker.patch.object(com, "_rm", rm_mock):
            result = com.open_device("NANIKA")
            assert result is True
            rm_mock.open_resource.assert_called_once_with("NANIKA")


testディレクトリの一つ上の階層で、

$ pytest

と打つと、テストが通ることが確認できると思います。

次に、open_resource()が実行された時にIOErrorが送出されるようにside_effectを設定します。

test/test_communication.py
import pytest

from src.communication import Communication


class TestCommunication(object):

    @pytest.fixture()
    def create_com(self):
        self._com = Communication()
        yield
        self._com = None

    def test_open_device(self, create_com, mocker):
        rm_mock = mocker.MagicMock()
        rm_mock.open_resource = mocker.Mock()
        with mocker.patch.object(self._com, "_rm", rm_mock):
            result = self._com.open_device("NANIKA")
            assert result is True
            rm_mock.open_resource.assert_called_once_with("NANIKA")

    def test_open_device_when_io_error(self, create_com, mocker):
        rm_mock = mocker.MagicMock()
        rm_mock.open_resource = mocker.Mock(side_effect=IOError())
        with mocker.patch.object(self._com, "_rm", rm_mock):
            result = self._com.open_device("NANIKA")
            assert result is False

Communicationクラスのインスタンスを作成するところは、fixtureにまとめています。

こちらもテストが通ると思います。

ちなみに

rm_mock = mocker.MagicMock()

の部分を、

rm_mock = mocker.Mock()

に変えると、with文のところでエラーが起きます。

self = <test_communication.TestCommunication object at 0x0000022DB34E5080>
mocker = <pytest_mock.MockFixture object at 0x0000022DB3602BA8>

    def test_open(self, mocker):
        com = Communication()
        rm_mock = mocker.Mock()
        rm_mock.open_resource = mocker.Mock()
>       with mocker.patch.object(com, "_rm", rm_mock):
E       AttributeError: __enter__

test\test_communication.py:12: AttributeError

Mock()のサブクラスのMagicMock()はここら辺のデフォルト値があらかじめ設定されているようです。

追記(2020/03/24)

pytest-mockのバージョンを2.0.0でテストを行うと、

with mocker.patch.object():

の部分でValueErrorが送出されます。

ValueError: Using mocker in a with context is not supported. https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager

mockerのwithコンテキスト内での使用はサポートされなくなったようです。
withを外せば問題なくテストを行うことができます。

mocker.patch.object()

参考URL

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?