【问题标题】:How do I patch an object so that all methods are mocked except one?如何修补对象以便模拟除一个之外的所有方法?
【发布时间】:2016-06-17 06:20:48
【问题描述】:

我有一个入口点函数调用它main 在一个我想保持未模拟的对象上,因为它在该对象上调用了几个其他方法:

class Thing(object):

    def main(self):
        self.alpha()
        self.bravo()

    def alpha(self):
        self.charlie()

    def bravo(self):
        raise TypeError("Requires Internet connection!")

    def charlie(self):
        raise Exception("Bad stuff happens here!")

这很容易手动模拟:

thing = Thing() 
thing.alpha = MagicMock()
thing.bravo = MagicMock()

我可以测试以确保 alpha 和 bravo 都被调用一次,我可以在 alpha 和 bravo 中设置副作用以确保它们被处理等等。

我担心的是如果代码定义发生变化并且有人向main 添加了charlie 调用。它没有被嘲笑,所以现在会感觉到副作用(它们就像写入文件,连接到数据库,从 Internet 获取东西,所以这个简单的异常不会提醒我测试现在坏的)。

我的计划是验证我的模拟对象除了我说它应该调用的方法之外没有调用其他方法(或引发测试异常)。但是,如果我这样做:

MockThing = create_autospec(Thing)
thing = Thing()
thing.main() 

print thing.method_calls
# [calls.main()] 

然后main 也被模拟,因此它不调用其他方法。除了主要方法,我如何模拟 每个方法? (我希望 method_calls 是 [calls.alpha(), calls.bravo()])。

编辑:用于破解答案

嗯,我有一个非常老套的解决方案,但我希望有比这更好的答案。基本上我重新绑定了原始类的方法(Python bind an unbound method

MockThing = create_autospec(Thing)
thing = MockThing()
thing.main = Thing.ingest.__get__(thing, Thing)
thing.main()

print thing.method_calls
# [calls.alpha(), calls.bravo()]

但必须有比使用函数描述符更简单的解决方案!

【问题讨论】:

  • 我收到错误消息“对象没有“摄取”属性,知道是什么原因造成的吗?我正在使用 Python 2.7

标签: python python-unittest python-mock


【解决方案1】:

当我做这些奇怪的事情时,比如调用我要模拟的类的真实方法时,我曾经调用方法静态引用:

mt = Mock(Thing)
Thing.main(mt)
print(mt.mock_calls)

[call.alpha(), call.bravo()]

无论如何,在编写完您的测试之后,最好使用一些协作者将您应该模拟的内容与您想要测试的内容分开:使用这些测试来引导生产代码重构,最后重构您的测试以删除这些脏东西测试。

【讨论】:

  • 调用静态版本的方法对我有帮助。非常感谢!
【解决方案2】:

我也遇到了同样的问题,但我想出了一个让我满意的方法。下面的示例使用上面列出的 Thing 类:

import mock

mock_thing = mock.create_autospec(Thing)
mock_thing.main = lambda x: Thing.main(mock_thing, x)

这将导致 mock_thing 调用属于 mock_thing 的实际“主”函数,但 mock_thing.alpha() 和 mock_thing.beta() 都将被称为 mock! (将 x 替换为您传递给函数的任何参数)。

希望对你有用!

【讨论】:

    【解决方案3】:

    假设你有一个Foo 类,带有一个frobnicate 实例方法。

    您可以使用functools.partial 创建Foo.frobnicate 函数的部分版本,并将您的模拟对象绑定到它的第一个参数。 这种方式允许您忽略参数的数量,并支持关键字参数(而lambda 需要您知道并显式传递参数,并且不支持 kwargs)。

    使用这种方法,最好定义一个创建模拟的make_mock_foo 函数,以便将通过functools.partial 的绑定保存在一个位置。

    import functools
    from unittest import mock
    
    
    class Foo:
        def frobnicate(self) -> None:
            pass
    
    
    def make_mock_foo() -> Foo:
        mock_foo = mock.MagicMock(spec=Foo)
        mock_foo.frobnicate = functools.partial(Foo.frobnicate, mock_foo)
        return mock_foo
    

    【讨论】:

      【解决方案4】:

      您似乎要么将单元测试纳入您的集成/功能测试,要么担心测试某个单元以外的其他内容。

      如果您正在对Thing 进行单元测试,那么您应该模拟您未测试的部分。但是,如果您正在测试 Thing 如何与其他事物(例如数据库)集成,那么您应该测试您的实际 Thing,而不是模拟的。

      另外,这也是依赖注入很有意义的地方,因为你会做这样的事情:

      class Thing:
          def __init__(self, db, dangerous_thing):
              self.db = db
              self.dangerous_thing = dangerous_thing
      
          #....
      
          def charlie(self):
              foxtrot = self.dangerous_thing.do_it()
      

      现在您可以在测试 Thing 时为 dangerous_thing 传递一个模拟,这样您就不必担心真的会执行 dangerous_thing

      【讨论】:

      • 谢谢你的建议,韦恩 - 但这并不能回答我的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-07-06
      • 2020-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-22
      • 1970-01-01
      相关资源
      最近更新 更多