web-dev-qa-db-ja.com

Python return_valueの代わりにMagicMockオブジェクトを返します

python file a.pyには、2つのクラスAおよびBが含まれます。

class A(object):
    def method_a(self):
        return "Class A method a"

class B(object):
    def method_b(self):
        a = A()
        print a.method_a()

ユニットテストをしたいmethod_bBをモックすることにより、クラスAで。ファイルの内容は次のとおりですtesta.py この目的のために:

import unittest
import mock
import a


class TestB(unittest.TestCase):

    @mock.patch('a.A')
    def test_method_b(self, mock_a):
        mock_a.method_a.return_value = 'Mocked A'
        b = a.B()
        b.method_b()


if __== '__main__':
    unittest.main()

Mocked A出力。しかし、私が得るのは:

<MagicMock name='A().method_a()' id='4326621392'>

どこで間違っていますか?

@mock.patch('a.A')の場合、テスト中のコードのクラスAを_mock_a_に置き換えています。

_B.method_b_で、a = A()を設定します。現在はa = mock_a()です。つまり、aは_return_value_の_mock_a_です。この値を指定していないため、通常のMagicMockです。これも設定されていないため、メソッドを呼び出すときにデフォルトの応答(まだ別のMagicMock)を取得します。

代わりに、_return_value_ of _mock_a_に適切なメソッドを設定し、次のいずれかの方法で実行できます。

_mock_a().method_a.return_value = 'Mocked A' 
    # ^ note parentheses
_

または、おそらくより明示的に:

_mock_a.return_value.method_a.return_value = 'Mocked A'
_

a.method_a()がモックメソッドをトリガーしたため、コードは_a = A_(インスタンスを作成せずにクラスを割り当てる)の場合に機能します。

54
jonrsharpe

pytest with mocker Fixture を好む。 pytestとmockerを使用した同じテストを次に示します。

_import a


class TestB:
    def test_method_b(self, mocker):
        mock_A = mocker.MagicMock(name='A', spec=a.A)
        mocker.patch('a.A', new=mock_A)
        mock_A.return_value.method_a.return_value = 'Mocked A'

        b = a.B()
        b.method_b()
_

私がテストを書いた方法は、テスト自体よりも興味深いと思うかもしれません-構文を手伝うために python library を作成しました。

体系的にあなたの問題に取り組んだ方法は次のとおりです。

あなたが望むテストと私のヘルパーライブラリから始めます:

_import a

from mock_autogen.pytest_mocker import PytestMocker


class TestB:
    def test_method_b(self, mocker):
        # this would output the mocks we need
        print(PytestMocker(a).mock_classes().prepare_asserts_calls().generate())

        # your original test, without the mocks
        b = a.B()
        b.method_b()
_

これでテストはあまり機能しませんが、印刷出力は便利です。

_# mocked classes
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
mock_B = mocker.MagicMock(name='B', spec=a.B)
mocker.patch('a.B', new=mock_B)
# calls to generate_asserts, put this after the 'act'
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_A, name='mock_A'))
print(mock_autogen.generator.generate_asserts(mock_B, name='mock_B'))
_

今、私はB()の呼び出しの前にAの単一のモックを配置し、その後に_generate_asserts_セクションを配置しています(前の印刷は不要なので、削除しました)それ):

_def test_method_b(self, mocker):
    # mocked classes
    mock_A = mocker.MagicMock(name='A', spec=a.A)
    mocker.patch('a.A', new=mock_A)

    # your original test, without the mocks
    b = a.B()
    b.method_b()

    # calls to generate_asserts, put this after the 'act'
    import mock_autogen
    print(mock_autogen.generator.generate_asserts(mock_A, name='mock_A'))
_

このテストの実行後、いくつかの貴重な入力が得られました。

_assert 1 == mock_A.call_count
mock_A.assert_called_once_with()
mock_A.return_value.method_a.assert_called_once_with()
mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()
_

最初の2行は、Aモックがパラメーターなしで1回初期化されたことを確認します。 3行目は_method_a_が呼び出されたことを確認しますが、4行目が最も役立つ場合があります。

_mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()
_

_method_a_の戻り値がstrprint関数による)で適用されていることがわかります。それを希望の文字列に置き換えるのはとても簡単です:

_mock_A.return_value.method_a.return_value = 'Mocked A'
_

そして、それが私が上記の完全なテスト方法に到達した方法です。

0
Peter K