Archive

[Python] 関数内部で生成されたインスタンスのメソッドの呼び出し方を mock でテストする

Python のテスト書いているときに関数内におけるインスタンスメソッドの呼び出しをテストをしたくなったらハマったので忘れないうちにメモ。pytest を使っているのでサンプルもそれ前提で書いている。

例えばこんなコードがあるとする ( target.py とする)

import use_library

def target_method(a, b):

    instanse = use_library.SampleClass(a)
    instanse.sample_method(b)

target_method は特に何も返さないのだけど、 内部でちゃんと instanse.sample_method が想定されるパラメータを受けて呼び出されるのかをテストしたい状況があった。

メソッド等がどうやって呼び出されたのかをテストするには Python だと mock を使えば簡単にできる。 ただ今回みたいに関数の内部で生成されたインスタンスのメソッドの場合どうやってモックすればいいのか結構悩んだ。

最初は以下のような感じで mock しようとした。
excepted はこのテストケースで instanse.sample_method の引数に渡っていて欲しい文字列。

from unittest import mock 

def test_target_method():

    my_mock = mock.Mock()
    excepted = 'B'

    with mock.patch('target.use_library', my_mock):
        import target
        target.target_method(a='A', b='B')

    my_mock.SampleClas().sample_method.assert_called_with(excepted)

このとき my_mocktarget.target_method 内部における use_library.SampleClass (use_librarymy_mock に差し替わってる) の呼び出しまでは記録してる。ただ use_library.SampleClass で初期化されたインスタンスである instanse に関するところまでは追跡できない。

なので my_mock.SampleClas().sample_method.assert_called_with('B') とかやっても「my_mock.SampleClas().sample_method は呼び出されてない」ということになってテストが失敗する。

可能であれば use_librarymy_mock に差し替わっている状況の with 構文のなかで instanse.sample_method.assert_called_with('B') みたいに呼び出せればいいのだけれどそうもいかない。

そんな感じであの手この手で色々試していくなかで、公式ドキュメントの unittest.mock のページ をよく見てみると mock_callsassert_has_calls というものがあることを発見した。

  • mock_calls の説明

    mock_calls は、メソッド、特殊メソッド、 そして 戻り値のモックまで、モックオブジェクトに対する すべての 呼び出しを記録します。

  • assert_has_calls の説明

    mock_calls は、メソッド、特殊メソッド、 そして 戻り値のモックまで、モックオブジェクトに対する すべての 呼び出しを記録します。

これらをを見て「なんとかなりそう!」と試してみたところ、うまくいった。

assert_has_calls の引数には mock.call オブジェクト を要素とするリスト型で渡す必要があるのでそこだけ注意。1 先程のテストコードを以下のような感じにするとうまく行く

from unittest import mock


def test_target_method():

    my_mock = mock.Mock()
    excepted = [mock.call.SampleClass().sample_method('B')]

    with mock.patch('target.use_library', my_mock):
        import target
        target.target_method(a='A', b='B')

    my_mock.assert_has_calls(excepted)

ちょっと書き方がややこしいけれど、 無事に関数内部のインスタンスメソッドの呼び出し方をテストできるようになった。


参考リンク

以下はこの記事の内容に直接触れてるわけではないけれど、Mock の基本的な使い方が非常によくまとまっているので貼っておく。


  1. print(my_mock.mock_calls) で実際に表示されているものに合わせて書いていくのが手っ取り早いと思う。