めんどくさいmock.patch()
unittest.mock
モジュールを正しく使って関数を置き換えるというのは以外と難しいもので、Pythonの名前空間について、しっかり把握できてないとうまくいかないことがある。
単純なケースでは、テスト対象のコードが参照している名前で置き換えてやればいい。 例えば
import spam def ham(): spam.egg()
というモジュール M
の ham()
をテストするために spam.egg
を置き換えるなら
def test(): import M with patch("spam.egg"): M.ham()
となる。また、
from spam import egg def ham(): egg()
のように egg
を参照している場合、ham()
の内部での egg
は M.egg
への参照なので
def test(): import M with patch("M.egg"): M. ham()
となる。
ここで注意しなければならないのが、関数を置き換える対象となるモジュールは、関数を呼び出すときに指定するモジュールではなく、関数が定義されたモジュールだということだ。
例えばもう一つのモジュール、M2
があって、
import M ham2 = M.ham
となっている場合がある。こんな場合でも、M2.ham2()
で呼び出される
egg()
を置き換えるには
def test(): import M2 with patch("M.egg"): M2.ham2()
のように、ham
を定義したモジュール M
の egg
を置き換えなければならない。
このように、機械的に mock.patch()
を使って置き換えることはできず、置き換える対象の関数がどのように呼び出されているか、ちゃんと調べてなくてはならない。普通はあまり気にしなくても良いのだが、たまに変な
import
などでどのモジュールを使っているのか調べにくいパッケージなどもある。そういう時は、
@contextlib.contextmanager def patch_globalref(func, target): m = MagicMock() with patch.dict(sys.modules[func.__module__].__dict__, **{target:m}): yield m
のような関数を用意しておいて、
def test(): import M2 with patch_globalref(M2.ham2, 'egg'): M2.ham2()
とように、テスト対象の関数オブジェクトのグローバルスコープを直接修正してしまうほうが手っ取り早い場合もある。