web-dev-qa-db-ja.com

python単体テストでクラス属性をモックするより良い方法

クラス属性を定義する基本クラスと、それに依存するいくつかの子クラスがあります。

class Base(object):
    assignment = dict(a=1, b=2, c=3)

私はこのクラスを異なるassignmentsで単体テストしたいと思います。空の辞書、単一項目など。これはもちろん非常に単純化されており、クラスやテストのリファクタリングの問題ではありません

私が思いついた(pytest)テストは、最終的に、その仕事は

from .base import Base

def test_empty(self):
    with mock.patch("base.Base.assignment") as a:
        a.__get__ = mock.Mock(return_value={})
        assert len(Base().assignment.values()) == 0

def test_single(self):
    with mock.patch("base.Base.assignment") as a:
        a.__get__ = mock.Mock(return_value={'a':1})
        assert len(Base().assignment.values()) == 1

これはかなり複雑でハッキングを感じます-私はそれがなぜ機能するのか完全には理解していません(しかし記述子に精通しています)。モックはクラス属性を記述子に自動的に変換しますか?

より論理的に感じるソリューションは機能しません。

def test_single(self):
    with mock.patch("base.Base") as a:
        a.assignment = mock.PropertyMock(return_value={'a':1})
        assert len(Base().assignment.values()) == 1

あるいは単に

def test_single(self):
    with mock.patch("base.Base") as a:
        a.assignment = {'a':1}
        assert len(Base().assignment.values()) == 1

私が試した他のバリアントも機能しません(テストでは割り当ては変更されません)。

クラス属性をモックする適切な方法は何ですか?上記よりも良い/より理解可能な方法はありますか?

21

base.Base.assignmentは、単にMockオブジェクトに置き換えられます。 madeそれは__get__メソッドを追加することにより記述子です。

それは少し冗長で少し不必要です。単にbase.Base.assignmentを直接設定できます:

def test_empty(self):
    Base.assignment = {}
    assert len(Base().assignment.values()) == 0

もちろん、テストの並行性を使用する場合、これはあまり安全ではありません。

PropertyMockを使用するには、次を使用します。

with patch('base.Base.assignment', new_callable=PropertyMock) as a:
    a.return_value = {'a': 1}

あるいは:

with patch('base.Base.assignment', new_callable=PropertyMock, 
           return_value={'a': 1}):
19
Martijn Pieters

読みやすくするには、@patchデコレータ:

from mock import patch
from unittest import TestCase

from base import Base

class MyTest(TestCase):
    @patch('base.Base.assignment')
    def test_empty(self, mock_assignment):
        # The `mock_assignment` is a MagicMock instance,
        # you can do whatever you want to it.
        mock_assignment.__get__.return_value = {}

        self.assertEqual(len(Base().assignment.values()), 0)
        # ... and so on

詳細は http://www.voidspace.org.uk/python/mock/patch.html#mock.patch で確認できます。

6
Dan Keder

内のクラス(たとえばキュー)がテスト内に既にインポートされている場合-パッチを適用する場合MAX_RETRYattr-@ patch.objectまたは単純に良い@ patch.multiple

from mock import patch, PropertyMock, Mock
from somewhere import Queue

@patch.multiple(Queue, MAX_RETRY=1, some_class_method=Mock)
def test_something(self):
    do_something()


@patch.object(Queue, 'MAX_RETRY', return_value=1, new_callable=PropertyMock)
def test_something(self, _mocked):
    do_something()
4
pymen

おそらく私は何かを見逃していますが、PropertyMockを使用しないとこれは不可能ですか?

with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
   # do stuff
3
igniteflow

Baseクラスを単体テストする方法の例を次に示します。

  • モック複数のクラス属性異なるタイプ(例:dictおよびint
  • @patchデコレータとpytestフレームワークとpython 2.7+または3+

# -*- coding: utf-8 -*-
try: #python 3
    from unittest.mock import patch, PropertyMock
except ImportError as e: #python 2
    from mock import patch, PropertyMock 

from base import Base

@patch('base.Base.assign_dict', new_callable=PropertyMock, return_value=dict(a=1, b=2, c=3))
@patch('base.Base.assign_int',  new_callable=PropertyMock, return_value=9765)
def test_type(mock_dict, mock_int):
    """Test if mocked class attributes have correct types"""
    assert isinstance(Base().assign_dict, dict)
    assert isinstance(Base().assign_int , int)
0
x0s