pythonサードパーティ製ライブラリシリーズの19--pythonテストで使用されるmockライブラリ


一、なぜmockが必要なのか
unittestを書くとき、システムに外部依存がたくさんある場合は、すべての部品を一度実行する必要もありません.例えば、マイクロブログに共有する機能を検証するには、テストのたびにインタフェースを実際に呼び出すと、効率が低いだけでなく、多くのゴミデータを製造し、外部要因によってunittestが失敗する可能性があります.時間がかかるシステムや、テスト環境を簡単に作成できないシステムでは、実際のテストは必要ありません.
コードが予想通りに実行され、関連する外部インタフェースが呼び出されたことを知る必要があります.やはりマイクロブログに共有する機能を例に挙げて、共有部分の偽コードはこのような可能性があります.
def share():
    """Share system generated message to weibo."""
    msg = generate_msg()
    weibo = get_weibo_client(user_id)
    weibo.upload(msg)
上記のコードをテストするときにすべてのコードを実行できる方法があるが、weiboは実際に実行されない.upload(msg)は、各関数が何回呼び出されているかを知ることができ、呼び出されるたびにパラメータをテストすると、使用例が便利になります.
pythonではmockはテスト時に外部サービスをシミュレートするために使用され、一般的に次のシーンではmockに使用されます.
a.データベース操作:毎回データベースを読み書きする必要はない
b.HTTPリクエスト:ネットワーク操作に時間がかかり、テスト時に外部のサービスに依存する
c.外部コマンド:ファイル操作、プロセス操作などのシステムコマンドを実行します.
二、mockの基本原理
前述したように、mockはコードの外部を置き換えるサービスです.pythonは動的言語であり、すべてがオブジェクトであるため、実行前にインスタンス、メソッド、関数、変数を置き換えます.例:
>>> import os

>>> def myremove(filename):
>>>     return filename

>>> os.remove = myremove
<function __main__.myremove>

>>> print os.remove('test-file')
test-file
上の例は最も簡単な説明で、myremoveをMockクラスに変更し、このクラスの中で呼び出したとき(複写__call_)伝達されたパラメータに基づいてその動作を決定することができ、呼び出しのたびに記録することができ、Mockが何をしたかを大体理解することができます.
三、mockの使用
(1)どのようにmockの1つの関数ですか?
from mock import Mock

myMethod = Mock()
myMethod.return_value = 3
myMethod(1, 'a', foo='bar')

myMethod.assert_called_with(1, 'a', foo='bar')    # True
myMethod()
myMethod.call_count                               # 2
mockに関数を出したい場合は、mockを直接使用します.Mock()インスタンスは、初期化時に戻り値myMethod=Mock(return_value=3)を設定するかmyMethod.return_valueのプロパティを設定します.
return以外はvalue、mock sideもできます.effect,side_effectは関数または異常であり、mockのオブジェクトが呼び出されると同じパラメータで呼び出されます.
myMethod = Mock(side_effect=KeyError('whatever'))
myMethod()


Traceback (most recent call last):
 ...
KeyError: 'whatever'
上の例はside_effectが関数であれば、この関数が呼び出され、戻り値を動的に生成するために使用できます.次の例mockは、入力文字列の長さを返す関数です.
def side_effect(str):
    return len(str)

myMethod = Mock(side_effect=side_effect)
myMethod('sd')              # 2
unittestの場合、mockは次のassert文も提供します.
assert_any_call
assert_called_once_with
assert_called_with
assert_has_calls
(2)どのようにして1つのクラスの方法をmockしますか?クラス内のメソッドをmockするには、mockが提供するpatchメソッドを使用します.
import mock
import Module1


@mock.patch.object(Module1.Class1, 'some_method')
def test(mock_method):
    mock_method.return_value = 3
    mock_method.side_effect = some_side_effect
    m = Module1.Class1()
    m.some_method(*args, **kwargs)


    assert m.some_method is mock_method
    m.some_method.assert_called_with(*args, **kwargs)

(3)どうやってクラスをmockしますか?すべてのプロパティとメソッドを含む関数またはクラスの動作をシミュレートする必要がある場合があります.手動で1つずつ追加すると、非効率でエラーが発生しやすくなります.mockはautospecの機能を提供し、提供されたテンプレートクラスに基づいてmockインスタンスを生成します.次はmockの関数の例です.
import mock

def myFunc(a, b, c):
    pass

>>> mock_func = mock.create_autospec(myFunc, return_value=3)
>>> mock_func(1,2,3)
>>> mock_func.assert_called_with(1,2,3)

>>> mock_func('a string')

Traceback (most recent call last):
 ...
TypeError: <lambda>() takes exactly 3 arguments (1 given)
mockクラスはこれと同じです.
>>> mock_class = mock.create_autospec(myClass)

(4)普段の使い方
ここでrequestsをシミュレートするには、戻り値が3に等しい.postネットワークインタラクションの戻り値.実際のネットワークインタラクションを省くことができます.もちろん、3という戻り値の代わりに1つの方法で値を返すこともできます.
import json
import mock
from django.test import TestCase

class ApiTest(TestCase):

    @mock.patch('apps.agent.requests.post')
    def test(self, mock_method):
        mock_method.return_value = 3
        mock_method.side_effect = some_side_effect
        res = self.client.post('/url/to/post')
        r = json.loads(res.content)
        self.assertEqual(0, r['retval'])