2014-09-01 29 views
23

是否有一個乾淨的方式來修補一個對象,以便在測試用例中獲得assert_call*幫助程序,而不實際刪除操作?蟒蛇模擬 - 修補方法不妨礙執行

例如,我怎麼能修改@patch一線得到下面的測試傳:

from unittest import TestCase 
from mock import patch 


class Potato(object): 
    def foo(self, n): 
     return self.bar(n) 

    def bar(self, n): 
     return n + 2 


class PotatoTest(TestCase): 

    @patch.object(Potato, 'foo') 
    def test_something(self, mock): 
     spud = Potato() 
     forty_two = spud.foo(n=40) 
     mock.assert_called_once_with(n=40) 
     self.assertEqual(forty_two, 42) 

我大概可以砍了一起使用side_effect,但我希望會有其工作的一個更好的方式所有的功能,classmethods,staticmethods,非綁定方法同樣的方式,等

+0

'foo'和'bar'沒有正確定義。他們應該是'def foo(self,n)'和'def bar(self,n)'。 – chepner 2014-09-01 14:38:44

+0

是的,謝謝...修正 – wim 2014-09-01 14:39:12

+0

另外,沒有太多的說斷言'foo'被調用,因爲測試本身正在調用它,而不是一些正在測試的其他代碼。同樣,測試'forty_two'是由* test *設置爲特定值,而不是測試代碼,似乎沒有多大價值。 – chepner 2014-09-01 14:46:39

回答

15

與你類似的解決方案,但使用wraps

def test_something(self): 
    spud = Potato() 
    with patch.object(Potato, 'foo', wraps=spud.foo) as mock: 
     forty_two = spud.foo(n=40) 
     mock.assert_called_once_with(n=40) 
    self.assertEqual(forty_two, 42) 

根據the documentation

包裝:項目爲模擬對象來包裝。如果換行不是無,則調用模擬將呼叫轉移到包裝對象 (返回實際結果)。模擬的屬性訪問將返回 模擬對象,該對象包裝對象 對象的對應屬性(因此嘗試訪問不存在的屬性將引發AttributeError,將引起 )。


class Potato(object): 

    def spam(self, n): 
     return self.foo(n=n) 

    def foo(self, n): 
     return self.bar(n) 

    def bar(self, n): 
     return n + 2 


class PotatoTest(TestCase): 

    def test_something(self): 
     spud = Potato() 
     with patch.object(Potato, 'foo', wraps=spud.foo) as mock: 
      forty_two = spud.spam(n=40) 
      mock.assert_called_once_with(n=40) 
     self.assertEqual(forty_two, 42) 
+0

謝謝,這比我的更好..你知道任何方式來做修補程序使用的修補程序,而不是在上下文管理器的使用? – wim 2014-09-01 15:15:25

+0

@wim,你的意思是? http://pastebin.com/pNyWRBNq – falsetru 2014-09-01 15:22:07

+1

不,因爲通過在裝飾器中創建一個新的馬鈴薯實例,您將失去實際正在測試的對象上的狀態,您需要綁定的方法.. – wim 2014-09-01 15:29:34

3

這個答案的地址從用戶Quuxplusone賞金提到的附加要求:

我用例的重要一點是,它與@patch.mock工作,即它不需要我在我構建Potato(本例中爲spud)和我的spud.foo的實例之間插入任何代碼。我需要spud從一開始就用模擬的foo方法創建,因爲我不控制spud的創建位置。

上述的使用方法可能沒有太多的麻煩用裝飾來實現:

import unittest 
import unittest.mock # Python 3 

def spy_decorator(method_to_decorate): 
    mock = unittest.mock.MagicMock() 
    def wrapper(self, *args, **kwargs): 
     mock(*args, **kwargs) 
     return method_to_decorate(self, *args, **kwargs) 
    wrapper.mock = mock 
    return wrapper 

def spam(n=42): 
    spud = Potato() 
    return spud.foo(n=n) 

class Potato(object): 

    def foo(self, n): 
     return self.bar(n) 

    def bar(self, n): 
     return n + 2 

class PotatoTest(unittest.TestCase): 

    def test_something(self): 
     foo = spy_decorator(Potato.foo) 
     with unittest.mock.patch.object(Potato, 'foo', foo): 
      forty_two = spam(n=40) 
     foo.mock.assert_called_once_with(n=40) 
     self.assertEqual(forty_two, 42) 


if __name__ == '__main__': 
    unittest.main() 

如果更換的方法接受正在測試修改可變參數,你可能希望初始化CopyingMock代替spy_decorator中的MagicMock