17
7

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.

[Python] pytest-mock使い方メモ

Last updated at Posted at 2020-12-27

インストール

poetryを使っていれば簡単です。

poetry add -D pytest-mock

使っていなくても簡単です。けれど、poetryのほうがおすすめです。

pip install pytest-mock
```

# テストの書き方

テストメソッドの引数として`mocker`を指定します。

```python
class MyClassTests:
    def test_something(self, mocker):
        pass
```

# モックの作り方

[モック - Mock](https://docs.python.org/ja/3/library/unittest.mock.html#the-mock-class)を作るには、`mocker.Mock`を使ってインスタンスを生成するやり方と、`mocker.patch`を使ってダイナミックにMockインスタンスに置き換えていくやり方があります。

題材として、このようなモジュールがあったとします。

```
exmock
├── __init__.py
├── bar.py
├── foo.py
└── target.py
```


```python:exmock/target.py
from .foo import FooClass
from .bar import BarClass


class TargetClass:
    def do_something(self, foo: FooClass) -> None:
        """ do_foo()の結果をdo_bar()に渡します。 """
        bar = BarClass()
        bar.do_bar(foo.do_foo())
```

## クラスのモックを作る

```python
foo_mock = mocker.Mock(spec=FooClass)
# foo_mock.do_foo もモックになっています。
```

specとしてクラスを指定することで、クラスに実在する属性のみが生えるようにします。
生えてきた属性も`Mock`のインスタンスです。



## コンストラクタの戻り値をモックにする

コンストラクタは値を返さないのです表現としておかしいですが、説明の都合上このようにしています。

```python
bar_mock = mocker.Mock(spec=BarClass)
mocker.patch('exmock.target.BarClass', return_value=bar_mock)
```

テスト対象ファイル**exmock/target.py**にある`from .bar import BarClass`にあわせて、`exmock.target.BarClass`をパッチしています。

## メソッドをモック化する

```python
do_bar_mock = mocker.patch.object(BarClass, 'do_bar')
```

`do_bar`メソッドの実装がモック化し、なにもしなくなります。

`do_bar`メソッドの動作を変更するには、戻り値のモックを使います。

# モックの動作を変更する

生成したモックは、テストにあわせて任意の動きをさせます。

## 戻り値を設定する

生成したモックの`return_value`属性に値をセットします。

```python
mymock.return_value = 戻り値
```

## プロパティの戻り値を設定する

直接値をセットすればOKです。

```python
mymock.foo = 戻り値
```

## 呼び出されたときに例外を発生させる

生成したモックの`side_effect`属性に例外をセットします。

```python
mymock.side_effect = Exception('エラーです')
```

## まるっと置き換える

モック対象の実装をまるっと置き換えます。

```python
def side_effect():
    pass

mymock.side_effect = side_effect
```

# モックのテスト

モックに対して、何らかの操作が行われたことをテストします。

## 1回呼び出されたことをテストする

```python
mymock.assert_called_once()
```

## 複数回呼び出されたことをテストする

```python
# pytestを使っている場合です。
assert mymock.call_count == 回数
```

## まったく呼び出されないことをテストする

```python
mymock.assert_not_called()
```

## 特定引数で呼び出されたことをテストする

```python
mymock.assert_called_with(特定引数1, 特定引数2, ……)
mymock.assert_called_once_with(特定引数1, 特定引数2, ……) # 1回呼び出される場合
```

## 複雑な引数をテストする

```python
args, kwargs = mymock.call_args
# argsやkwargsをテストする
```

`call_args`に、直近の呼び出し引数が入ってます。
`args`は、キーワードなし引数が入っているタプルです。`kwargs`は、キーワード付き引数が入っているディクショナリです。



# 検証環境

この記事を書くにあたって使った環境です。

* Python 3.8.2
* Poetry 1.1.4
* pytest-mock 3.4.0

使ったファイルはこちらに置きました。
https://github.com/sengokyu/python-pytest-mock-usage-example
17
7
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
17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?