雑念ストレージ

プログラミング関連のメモとか

【Python】MagicMockの使い方メモ2 - ユニットテストでの使い方とpatch

ユニットテストでは、開発環境から呼び出せない処理はモックに差し替えてテストしたい。
たとえば、外部のAPIの呼び出しやファイルの読み書きなどをモックにすることが多い。

モックにするやり方が色々あるので、簡単にまとめる。

インスタンスのメソッドをMagicMockで置き換える

以下のhoge_funcメソッドをモックにしてみる。

class Hoge:
    def hoge_func(self):
        return 'called func'

hoge_funcメソッドにMagicMockを設定してみる。
メソッドがモックに置き換わり、戻り値が'called mock'になっている。

def test(self):
    hoge = Hoge()
    hoge.func = MagicMock(return_value='called mock')
    self.assertEqual(hoge.func(), 'called mock')

クラスメソッドをMagicMockで置き換える

クラスメソッドもモック化できる。
たとえば、以下のようにクラスメソッドを持つクラスがあったとする。

class Hoge2:
    @classmethod
    def class_func(cls):
        # 1から10のランダムな数値を返す
        return random.randint(1, 10)

クラスメソッドをMagicMockで置き換えれば、動作を変えることができる。

def test(self):
    print(Hoge2.class_func())
    # 3

    Hoge2.class_func = MagicMock(return_value=99)

    print(Hoge2.class_func())
    # 99

対象メソッドのパスを指定してモック化する(patch)

ユニットテストで、モック化したいインスタンスのメソッドにアクセスできることは少ないと思う。
大抵は、テストしたいモジュールから呼び出される別のモジュールをモック化したいはず。

たとえば、以下のようなコードを考えてみる。

class Hoge:
    def hoge_func(self):
        # 1から10のランダムな数値を返す
        return random.randint(1, 10)

class Fuga:
    def __init__(self):
        self.hoge = Hoge()

    def fuga_func(self):
        val = self.hoge.hoge_func()
        # 2倍にして返す
        return val * 2

Fugaクラスはインスタンス変数にHogeのオブジェクトを持っている。
Fugaクラスのfuga_funcのテストをしたい場合、Hogeクラスのhoge_funcの返す値がランダムなのでテストし辛い。

このような場合、patchhoge_funcをモック化すると楽にテストできる。

def test(self):
    with patch('module.Hoge.hoge_func') as hoge_func_mock:
        # hoge_funcの戻り値を10に固定する
        hoge_func_mock.return_value = 10

        fuga = Fuga()
        self.assertEqual(fuga.fuga_func(), 20)

patchは以下のようにデコレータとして書くこともできる。

@patch('module.Hoge.hoge_func', return_value=5)
def test(self, hoge_func_mock):
    fuga = Fuga()
    self.assertEqual(fuga.fuga_func(), 10)

デコレータで指定したモックはユニットテストの引数として記載する必要がある。(hoge_func_mockの部分)
複数のデコレータを設定した場合は、 デコレータでで書いた逆順 に引数を書く必要があるので注意する。