環境
- 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メソッドを持っており、これのテストを考えます。
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に置き換えます。
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を設定します。
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()