mocker.patchではまってわかったimportの仕組み
mocker.patchで関数の返り値を指定したかったんだけど、うまくいかずはまった。調べてたらimport
が正しく理解できていないことがわかったので、メモ。
コード
utils.py(モック化したいもの)
def utilA(): return "returnA"
sample.py(テストしたいもの)
from utils import * def methodA(): str = utilA() return str
test_sample.py(テストコード)
from sample import * def test_A(mocker): mocker.patch('utils.utilA', return_value="mock_returnA") print(methodA())
うまくいかなかったこと
関数methodA
のテストのため、utilA
の戻り値をreturnA
ではなくmock_returnA
にしたかった。
mocker.path
の第1引数はモジュール+オブジェクト名を書けば良い。今回モックにしたいのはutilsモジュールのutilA関数なので
utils.utilA
であっているはず。
だけどテストを実行すると、
# pytest tests/testsample.py -s … tests/testsample.py returnA
と、returnA
と出てきてしまった。これは通常通りutilA
が実行されてしまっているのでモックが動いていない。なぜなのか。
うまくいった
いろいろ試した結果、mocker.path
の第1引数を
sample.utilA
と書けば、うまくutilAがモック化されることがわかった。
from sample import * def test_A(mocker): mocker.patch('sample.utilA', return_value="mock_returnA") print(methodA())
# pytest tests/testsample.py -s … tests/testsample.py mock_returnA
なぜなのか
ポイントはimport
だった。自分めんどくさがりなので、自分のモジュールを読み込みたいときはfrom utils import *
のようにアスタリスクを使ってインポートすることが多い。
これがどういうことなのかあまり深く考えたことなかったけど、sampleモジュールにutilsモジュールのオブジェクトを展開しているということのようだ。
なのでutilsモジュールのutilA
ではなくsampleモジュールのutilA
を使っているイメージなようだ。
なのでmocker.patch
にもmocker.patch('sample.utilA'
と書かないと使っているutilAがモック化されない。
なお、PEP8ではこのアスタリスクでのインポートは推奨されていないらしい。
https://pep8-ja.readthedocs.io/ja/latest/#import
ワイルドカードを使った import (from
import *) は避けるべきです。なぜなら、どの名前が名前空間に存在しているかをわかりにくくし、コードの読み手や多くのツールを混乱させるからです。
sample.pyのimport文をimport utils
とし、utils.utilA
を呼び出すコードにするなら、mocker.patch('sample.utilA'
でうまくいく。
sample.py
from utils import def methodA(): str = utils.utilA() return str
from sample import * def test_A(mocker): mocker.patch('utils.utilA', return_value="mock_returnA") print(methodA())
# pytest tests/testsample.py -s … tests/testsample.py mock_returnA