web-dev-qa-db-ja.com

pythonクラスのインスタンスのメソッドをモックアウトする

テストを容易にするために、プロダクションコード内のクラスの任意のインスタンスのメソッドをモックアウトしたいと思います。 Pythonにこれを容易にするライブラリはありますか?

基本的に、私は次のことをしたいのですが、Python(次のコードはRubyで、Mochaライブラリを使用しています)で:

  def test_stubbing_an_instance_method_on_all_instances_of_a_class
    Product.any_instance.stubs(:name).returns('stubbed_name')
    assert_equal 'stubbed_name', SomeClassThatUsesProduct.get_new_product_name
  end

上で注意すべき重要な点は、テストしているものによって作成されたインスタンスのメソッドを実際にモックアウトする必要があるため、クラスレベルでモックアウトする必要があるということです。

使用事例:

QueryMakerのインスタンスでメソッドを呼び出すRemoteAPIクラスがあります。 RemoteAPI.get_data_from_remote_serverメソッドをモックアウトして定数を返したいのですが。 RemoteAPIコード内に特別なケースを入れずに、それが実行されている環境を確認せずに、テスト内でこれを行うにはどうすればよいですか。

私が行動したいものの例:

# a.py
class A(object):
    def foo(self):
        return "A's foo"

# b.py
from a import A

class B(object):
    def bar(self):
        x = A()
        return x.foo()

# test.py
from a import A
from b import B

def new_foo(self):
    return "New foo"

A.foo = new_foo

y = B()
if y.bar() == "New foo":
    print "Success!"
35
Jamie Wong

最も簡単な方法は、おそらくクラスメソッドを使用することです。実際にはインスタンスメソッドを使用する必要がありますが、インスタンスメソッドを作成するのは面倒ですが、クラスメソッドを作成する組み込み関数があります。クラスメソッドでは、スタブは最初の引数として(インスタンスではなく)クラスへの参照を取得しますが、スタブであるため、これはおそらく問題ではありません。そう:

_Product.name = classmethod(lambda cls: "stubbed_name")
_

ラムダの署名は、置き換えるメソッドの署名と一致する必要があることに注意してください。また、もちろん、Python(Rubyのように)は動的言語であるため、インスタンスを手に入れる前に誰かがスタブされたメソッドを他のものに切り替えないという保証はありません。が、そうなった場合はすぐにわかると思います。

編集:さらなる調査では、classmethod()を省略できます:

_Product.name = lambda self: "stubbed_name"
_

元のメソッドの動作をできる限り忠実に維持しようとしましたが、実際には必要ないように見えます(そして、とにかく期待どおりに動作を維持していません)。

3
kindall

テストが非常に一般的で、Pythonでそれを支援するためのツールがたくさんあるときに、メソッドをモックアウトする必要があります。このような「モンキーパッチ」クラスの危険性は、後でundoしないと、クラスが他のすべての用途に合わせて変更されていることです。テスト。

私のライブラリモックは、最も一般的なPythonモックライブラリ)の1つであり、テスト中にオブジェクトやクラスのメソッドまたは属性に安全にパッチを適用するのに役立つ「パッチ」と呼ばれるヘルパーが含まれています。

モックモジュールは以下から入手できます:

http://pypi.python.org/pypi/mock

パッチデコレータは、コンテキストマネージャまたはテストデコレータとして使用できます。これを使用して、自分で関数をパッチするか、非常に構成可能なMockオブジェクトを自動的にパッチすることができます。

from a import A
from b import B

from mock import patch

def new_foo(self):
    return "New foo"

with patch.object(A, 'foo', new_foo):
    y = B()
    if y.bar() == "New foo":
        print "Success!"

これにより、パッチの解除が自動的に処理されます。あなたはモック関数を自分で定義せずに逃げることができます:

from mock import patch

with patch.object(A, 'foo') as mock_foo:
    mock_foo.return_value = "New Foo"

    y = B()
    if y.bar() == "New foo":
        print "Success!"
55
fuzzyman

モックはその方法です。クラスから作成されたインスタンスのインスタンスメソッドにパッチを適用していることを確認するのは少し難しいかもしれません。

# a.py
class A(object):
    def foo(self):
        return "A's foo"

# b.py
from a import A

class B(object):
    def bar(self):
        x = A()
        return x.foo()

# test.py
from a import A
from b import B
import mock

mocked_a_class = mock.Mock()
mocked_a_instance = mocked_a_class.return_value
mocked_a_instance.foo.return_value = 'New foo'

with mock.patch('b.A', mocked_a_class):  # Note b.A not a.A
    y = B()
    if y.bar() == "New foo":
        print "Success!"

docs で参照されている、「パッチを適用したクラスのインスタンスのメソッドの戻り値を設定するには...」のパラグラフ

3
Fush