2014-09-01 28 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