2012-08-31 92 views
12

我嘗試使用mock在python中編寫一些單元測試。如何用python模擬庫模擬基類

比如我有下面的類:

class TCPHandler(socketserver.BaseRequestHandler): 
    def handle(self): 
     self.data = self.request.recv(1024).strip() 

我只是想測試handle方法。無需假設任何有關socketserver.BaseRequestHandler的信息。我例如要聲稱handle調用recv與論點1024。模擬可以做這樣的事情嗎?即用模擬替換基類socketserver.BaseRequestHandler?或者我偏離了這個想法?


隨着ecatmur的答案(謝謝!)我第一次嘗試以下操作:

patcher = patch.object(TCPHandler, '__bases__', (Mock,)) 
with patcher: 
    patcher.is_local = True 
    handler = TCPHandler() 
    handler.handle() 

但現在handle不叫anylonger和dir(handler)給出:

['assert_any_call', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect'] 

type(handler)<class 'mock.TCPHandler'>

我認爲修補基類會導致我的派生類變成模擬。


我現在給了另一種思路一試:

mock = MagicMock() 
TCPHandler.handle(mock) 
#assertions 

但是模擬似乎並沒有被調用。

def test_derived(): 
    patcher = mock.patch.object(Derived, '__bases__', (mock.Mock,)) 
    with patcher: 
     patcher.is_local = True 
     d = Derived() 
     print d.foo() 

is_local黑客有必要從試圖扭轉補丁時要調用delattr停止mock.patch

回答

17

您可以通過打補丁的派生類的__bases__做到這一點。

+0

我跟着你的提示,但結果是,派生類的方法也被模擬所取代。 –

+0

如果你可能看一下,我把我當前的測試代碼放在問題中?我感謝您的幫助。 –

+0

@FrederickRoth不太確定; Python 2對我來說工作正常。也許嘗試嘲笑方法訪問的對象的部分? – ecatmur

4

我認爲問題在於你試圖嘲笑你想測試的實際代碼。而不是該代碼正在調用的對象。如果您有興趣查看句柄方法是否調用self.request上的recv方法,然後嘲笑recv方法。

def test_tcp_handler_method(self): 

    handler = TCPHandler() 
    handler.request = Mock() 

    handler.handle() 

    self.assertTrue(handler.request.recv.called) 
    self.assertEqual(handler.request.recv.call_args[0], 1024) 

你可能需要做一些額外的設置以獲得處理程序來實例化,但基本思想應該清楚。

+0

感謝您的輸入! TCPHandler需要一個tcprequest來實例化,因此不會直接由我的代碼實例化,而是由TCPServer實例化。這就是我不想調用其__init__的全部原因。我只想測試我自己的小方法,而不用測試整個tcpserver框架。 –

4

我不知道這是否是最好的解決方案,但我管理使用type()重新定義與以前的父類不同。我建立了一個叫patch_parent()功能,即與父母模擬返回類:

from contextlib import contextmanager 

@contextmanager 
def patch_parent(class_): 
    """ 
    Mock the bases 
    """ 
    yield type(class_.__name__, (Mock,), dict(class_.__dict__)) 

在此之後,您可以使用patch_parent這樣的:

class Bar(): 
    def method(self, param1, param2...): 
     ... 

class Foo(Bar): 
    pass 


>>> with patch_parent(Foo) as MockFoo: 
...  f = MockFoo() 
...  print f 
...  print f.method() 
... 
<Foo id='15488016'> 
<Foo name='mock.method()' id='15541520'> 
>>> s = Foo() 
>>> s.method() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: method() takes exactly 3 arguments (1 given) 

MockFoo類仍然具有的方法Foo類,並且它沒有在父級中定義的方法,因爲父級現在是Mock類。