2011-02-18 8 views
15

테스트를 용이하게하기 위해 프로덕션 코드의 일부 클래스 인스턴스에 대해 메소드를 조롱하고 싶습니다. 이것을 용이하게 할 수있는 파이썬 라이브러리가 있습니까?파이썬 클래스의 모든 인스턴스에 대한 메소드 모방

는 기본적으로, 다음을 수행하기를 원하지만 파이썬 (다음 코드는 모카 라이브러리를 사용하여, 루비)에서 :

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 

위에서 주목해야 할 중요한 것은 내가 그것을 조롱 필요가있다 클래스 레벨에서, 내가 실제로 테스트하고있는 것으로 생성 된 인스턴스의 메소드를 조롱 할 필요가 있기 때문에.

사용 케이스 :

나는 RemoteAPI의 인스턴스의 메소드를 호출하는 클래스 QueryMaker 있습니다. 일부 상수를 반환하는 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!" 

답변

1

가장 쉬운 방법입니다 클래스 메서드를 사용하는 것 같습니다. 인스턴스 메소드를 사용해야하지만, 클래스 메소드를 생성하는 내장 함수가있는 반면, 인스턴스 메소드를 사용해야한다. 클래스 메소드를 사용하면 스텁이 첫 번째 인수로 인스턴스가 아닌 클래스에 대한 참조를 가져 오지만, 스텁이므로이 값은 중요하지 않습니다. 따라서 :

Product.name = classmethod(lambda cls: "stubbed_name") 

람다의 서명은 대체하려는 메서드의 서명과 일치해야합니다. 물론 파이썬 (Ruby와 같은)은 동적 인 언어이기 때문에 인스턴스에 손을 넣기 전에 다른 사람이 스텁 메소드를 전환하지 않을 것이라는 보장은 없습니다. 그럴 경우.

편집 : '

Product.name = lambda self: "stubbed_name" 

나는 가능한 한 가깝게 원래의 방법의 동작을 유지하기 위해 노력했지만, 실제로 필요가 없습니다 것 같습니다 (그리고 아무튼 : 추가 조사에서, 당신은 밖으로 떠나 classmethod() 수 있습니다 여하튼, 내가 바라던대로 행동을 보존하지 않는다).

+0

"... 패치 된 클래스의 인스턴스의 방법에 반환 값을 구성하려면"파라의 시작에서의 docs에서 참조. 이것이 나의 유스 케이스를 만족 시키는가? –

+1

그것은해야한다; 귀하의 유스 케이스는 귀하의 예와 거의 같습니다. 기본적으로 클래스에서 메소드의 정의를 변경하면 모든 인스턴스에서 메소드의 정의도 변경됩니다. 'classmethod' 함수는 기본적으로 함수를 메서드로 변환하는 래퍼를 생성하고'lambda'는 단순히 정적 문자열을 반환하는 함수를 정의합니다. – kindall

+0

...하지만 내 편집을 참조하십시오. – kindall

1

정확히 무엇을 하려는지 Ruby에 대해 알 수는 없지만 __getattr__ 방법을 확인하십시오. 클래스에서 정의한 경우 Python은 코드가 클래스의 다른 속성에 액세스하려고 시도 할 때이를 호출합니다. 메소드가되기를 원하기 때문에 반환하는 메소드를 즉석에서 작성해야합니다.

>>> class Product: 
...  def __init__(self, number): 
...   self.number = number 
...  def get_number(self): 
...   print "My number is %d" % self.number 
...  def __getattr__(self, attr_name): 
...   return lambda:"stubbed_"+attr_name 
... 
>>> p = Product(172) 
>>> p.number 
172 
>>> p.name() 
'stubbed_name' 
>>> p.get_number() 
My number is 172 
>>> p.other_method() 
'stubbed_other_method' 

또한 존재하지 않는 속성에 대한 __getattr__를 호출 __getattr__ 요구에 하지이 클래스의 다른 정의되지 않은 속성을 사용, 그렇지 않으면 무한 재귀 않습니다.이것은 당신은 당신의 테스트 코드가 아닌 생산 코드에서하고 싶은 일 경우

...  def __getattr__(self, attr_name): 
...   return self.x 
>>> p.y 
Traceback (most recent call last): 
#clipped 
RuntimeError: maximum recursion depth exceeded while calling a Python object 

, 다음 다음 테스트 코드에 __getattr__ 방법 (언 바운드)를 정의, 생산 코드 파일에 일반 클래스 정의를 넣어 한 다음 바인딩 클래스에 당신이 원하는 :

#production code 
>>> class Product: 
...  def __init__(self, number): 
...   self.number = number 
...  def get_number(self): 
...   print "My number is %d" % self.number 
...   

#test code 
>>> def __getattr__(self, attr): 
...  return lambda:"stubbed_"+attr_name 
... 
>>> p = Product(172) 
>>> p.number 
172 
>>> p.name() 
Traceback (most recent call last): 
    File "<interactive input>", line 1, in <module> 
AttributeError: Product instance has no attribute 'name' 
>>> Product.__getattr__ = __getattr__ 
>>> p.name() 
'stubbed_name' 

나는이 모두가 있는지 여부를 속성에 대한 __getattribute__가 호출, __getattr__ 반대로 이미 (__getattribute__를 사용하여 한 클래스와 어떻게 반응하는지 모르겠어요 그들은 존재한다).

만 이미 존재하는 구체적인 방법에 대해이 작업을 수행하려면, 당신은 같은 것을 할 수있는 :

#production code 
>>> class Product: 
...  def __init__(self, number): 
...   self.number = number 
...  def get_number(self): 
...   return self.number 
...  
>>> p = Product(172) 
>>> p.get_number() 
172 

#test code 
>>> def get_number(self): 
...  return "stub_get_number" 
... 
>>> Product.get_number = get_number 
>>> p.get_number() 
'stub_get_number' 

을 또는 당신이 정말 우아하고 싶다면, 당신은 일을 만들 수있는 래퍼 함수를 ​​만들 수 있습니다 쉽게 여러 방법 : 테스트는 매우 일반적이며, 파이썬에서 당신을 도울 수있는 도구가 많이있을 때 방법을 조롱 할 필요

#test code 
>>> import functools 
>>> def stubber(fn): 
...  return functools.wraps(fn)(lambda self:"stub_"+fn.__name__) 
... 
>>> Product.get_number = stubber(Product.get_number) 
>>> p.get_number() 
'stub_get_number' 
+0

이 연습의 목표는 프로덕션 코드를 테스트 할 수 있도록하기 위해 프로덕션 코드를 수정하지 않는 것입니다. 네가 제안한 것이 그 일을 성취하지 못한다고 확신한다. –

+0

그게 뭘 원하는지, 나는 당신이 테스트 코드에서 언 바운드 함수로 정의한 다음'Product .__ getattr__ = fn_name'을 통해 테스트 코드의 클래스에 바인딩 할 수 있다고 확신한다. – user470379

+0

나는' __getattr__'이 (가) 사용 된 이유는 유용하다고 생각하는 이유를 정말로 이해하지 못합니다. 메소드를 모의 할 필요가있는 클래스에 이미 정의 된 메소드를 대체해야합니다. –

35

. 이와 같은 "원숭이 패치"클래스의 위험은 을 실행 취소하지 않고 후에 실행하면 테스트를 통해 다른 모든 용도로 클래스가 수정 된 것입니다.

가장 인기있는 파이썬 조롱 라이브러리 중 하나 인 My library mock에는 테스트 중에 객체 및 클래스의 메소드 또는 속성을 안전하게 패치하는 데 도움이되는 "patch"라는 도우미가 포함되어 있습니다.

http://pypi.python.org/pypi/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!" 
+0

Ahh - 감사합니다. 나는 '모의'를 보았지만 다른 곳으로 가져 오는 물건에 패치를 적용하는 데 사용 된 명백한 예는 보지 못했습니다. +1 –

+0

테스트에서 직접적인 원숭이 패치에주의하십시오. mock.patch는 현재 네임 스페이스 ('patch.object'와 함께) 나 name :'patch ('mymodule.myclass')'에 의해 지정된 다른 네임 스페이스에있는 어떤 네임 스페이스에있는 것들을 패치하는데 사용될 수 있습니다. – fuzzyman

+3

알아야 할 중요한 사항은 이름으로 패치하는 경우 정의 된 모듈이 아닌 사용 된 * 모듈의 이름을 패치하는 것입니다. 예 : 'SomeClass' 모듈이'bar' 모듈에 정의되었지만'foo' 파일로 임포트되는 경우에도 'foo' 모듈에서'SomeClass'의 사용을 패치하기를 원한다면'foo.SomeClass'와 * *'바. 초등학생 '. 클래스 메소드를 패치 할 때 클래스 * 객체 *가 두 모듈에서 같은 객체가 될만큼 중요하지 않습니다. 다른 것들은 대단히 중요합니다. – fuzzyman

1

모의 방법은 괜찮습니다. 클래스에서 생성 된 인스턴스의 인스턴스 메서드를 패치하는 것은 약간 까다로울 수 있습니다.

# 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!" 

내가 정확히 어떻게 작동하는지 다음 아니에요

관련 문제