web-dev-qa-db-ja.com

インスタンスのメソッドがモックで呼び出されたことをテストする

私は別のクラスを使用しているクラスをテストしているこの種の設定を持っています、そして私は後者を模擬したいので最初のクラス自体だけをテストしています。

nuclear_reactor.py

class NuclearReactor():
    def __init__(self):
        print "initializing the nuclear reactor"

    def start(self):
        print "starting the nuclear reactor"

nuclear_manager.py

from nuclear_reactor import NuclearReactor

class NuclearManager():
    def __init__(self):
        print "manager creating the nuclear reactor"
        self.reactor = NuclearReactor()

    def start(self):
        print "manager starting the nuclear reactor"
        self.reactor.start()

test_nuclear_manager.py

from mock import Mock
import nuclear_manager
from nuclear_manager import NuclearManager

def test():
    mock_reactor = nuclear_manager.NuclearReactor = Mock()
    nuke = NuclearManager()
    nuke.start()
    nuke.start()
    print mock_reactor.mock_calls
    print mock_reactor.start.call_count

test()

テストしたいのは、NuclearReactor.startが呼び出されることですが、これを実行すると、次のようになります。

manager creating the nuclear reactor
manager starting the nuclear reactor
manager starting the nuclear reactor
[call(), call().start(), call().start()]
0

startはインスタンスの属性であり、クラスの属性ではないので完全に理解できます。mock_callsを解析することはできますが、インスタンス化された呼び出しを確認するより良い方法はありません偽のクラスが作られる?

NuclearManagerで依存性注入を使用してモックNuclearReactorを渡すことができますが、モックだけを使用する別の方法があると考えています。

9
sagism

実際、startが呼び出されているかどうかをテストしていますクラスで直接が呼び出されていますが、コードでは呼び出されていません。インスタンスのメソッドを直接テストできます。インスタンスはクラスを呼び出すことによって生成されることに注意してください。

_print mock_reactor.return_value.calls
print mock_reactor.return_value.start.call_count
_

_Mock.return_value_属性 は、モックされたクラス、つまりインスタンスの呼び出しの結果です。

単にcallモックにすることもできます。デフォルトでは、モックは呼び出されたときに常にまったく同じオブジェクトを返し、新しいモックはその戻り値を表します。

_print mock_reactor().calls
print mock_reactor().start.call_count
_

モックインスタンスとモックインスタンスの_return_value_属性を呼び出した結果は、まったく同じです。

NuclearReactorモックへの呼び出しを出力することにより、すでに正しいパスにいたので、_calledモックでstart()が呼び出された詳細を見逃しました。 call().start()ではなくstart()が記録されました。

直接割り当てではなく、 mock.patch() を使用してパッチを処理することもできます。これにより、パッチが削除済みであることを確認し、他のテストがモック対象を独自に決定できるようにします。

_import mock
from nuclear_manager import NuclearManager

@mock.patch('nuclear_manager.NuclearReactor')
def test(mock_reactor):
    nuke = NuclearManager()
    nuke.start()
    nuke.start()

    instance = mock_reactor.return_value
    assert instance.start.call_count == 2
    instance.assert_called()
_

ここではデコレータとして使用しました。 test()関数が呼び出されるとモックが配置され、関数が終了すると再び削除されます。 patch()をコンテキストマネージャとして使用して、パッチの範囲をさらに細かく制限することもできます。

また、このようなユニットテストでは、 unittest library を使用してください。

_import mock
import unittest
import nuclear_manager

class NuclearManagerTests(unittest.TestCase):
    @mock.patch('nuclear_manager.NuclearReactor')
    def test_start(self, mock_reactor):
        nuke = NuclearManager()
        nuke.start()
        nuke.start()

        instance = mock_reactor.return_value
        self.assertEqual(instance.start.call_count, 2)
        instance.assert_called()

if __name__ == '__main__':
    unittest.main()
_

これにより、テストをより大きなテストスイートに適合させ、テストを有効または無効にして、他のテストツールと統合できます。

8
Martijn Pieters