2011-02-18 48 views
15

我想模擬生產代碼中某些類的任何實例上的方法,以便於測試。 Python中是否有任何可以促進這一點的庫?嘲笑python類的任何實例的方法

基本上,我要做到以下幾點,但在Python(下面的代碼是紅寶石,採用摩卡庫):

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

回答

1

最簡單方法是可能要使用類方法。你真的應該使用一個實例方法,但是創建它們是一件很痛苦的事情,而有一個內置的函數可以創建一個類方法。使用類方法,你的存根將作爲第一個參數獲得對類的引用(而不是實例),但由於它是一個存根,這可能並不重要。所以:

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

請注意,lambda的簽名必須與您要替換的方法的簽名匹配。另外,當然,由於Python(如Ruby)是一種動態語言,因此不能保證在你開始實例之前,別人不會爲其他東西切換其他東西,儘管我希望你能很快知道如果發生的話。

編輯:在進一步的調查,你可以離開了classmethod()

Product.name = lambda self: "stubbed_name" 

我試圖儘可能地保留原始方法的行爲,但它看起來像它實際上不是必要(和沒有按」無論如何,我都會保持這種行爲)。

+0

我不完全以下是如何工作的。這是否滿足我的用例? – 2011-02-18 02:14:43

+1

它應該;你的用例與你的例子幾乎相同。基本上,通過改變類的方法的定義,你也改變了它在所有實例上的定義。 'classmethod`函數創建一個基本上將函數轉換爲方法的包裝器,'lambda`定義函數來簡單地返回一個靜態字符串。 – kindall 2011-02-18 02:18:47

+0

...但看到我的編輯。 – kindall 2011-02-18 02:34:20

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' 
35

孤男寡女模擬出方法時,測試是非常常見的,有很多工具可以幫助你用它在Python。像這樣的「猴子補丁」類的危險是,如果你不撤消這之後則類已被修改以便在整個測試中的所有其他用途。

我的圖書館模擬,這是最流行的Python嘲諷的圖書館之一,包括被稱爲「補丁」助手,可以幫助您安全地修補方法或在您的測試對象和類屬性。

該模擬模塊可以從:

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

引用在docs,在對位出發的「在對補丁的類實例的方法來配置返回值......」