Python:在mock patch类装饰器上调用stop
Mock的文档介绍了一种简单而优雅的方法,可以将补丁应用到一个TestCase
里的所有测试方法上:
@patch('foo.bar')
@patch('foo.baz')
@patch('foo.quux')
@patch('foo.narf')
class FooTest(TestCase):
def test_foo(self, bar, baz, quux, narf):
""" foo """
self.assertTrue(False)
不过,我在使用这种方法时遇到了一个问题,就是如果我想在某个测试方法里调用stop()来停止某个补丁,似乎没有办法获取到补丁对象的引用——传入方法的只有模拟对象,比如bar
、baz
、quux
、narf
。
我找到的唯一解决这个问题的方法是,按照Mock文档中描述的模式,在TestCase
的setUp
方法里实例化并启动补丁,然后在tearDown
方法里停止它。这虽然能满足我的需求,但增加了很多额外的代码,看起来没有使用类装饰器的方法那么优雅。
有没有其他方法可以解决这个问题呢?
1 个回答
1
假设你想在一个方法里暂时恢复 foo.narf
。在这个被装饰的函数中,foo.narf
是一个 MagicMock
对象。这个对象有一个叫 _mock_wraps
的属性,当你调用这个 mock 对象时,它会被触发!所以在你的模块顶部,你可以写 _narf = foo.narf
,然后在你的测试案例中,设置 foo.narf._mock_wraps = _narf
。
但这里有个问题,这样做只会让它通过到真正的函数,而不是把它真正换回来,这就意味着有些测试案例会失败(例如,如果它们依赖于函数对象实际上是“它自己”)。而且如果你的 mock 对象还有其他属性,这可能会造成干扰(我没有做太多测试),因为调用 _mock_wraps()
是在一个方法的底部,而这个方法首先会考虑 mock 的其他属性。
2
patch()
装饰器涉及到每个 patcher
(每个方法都有独立的副本)被添加到一个叫 patchings
的列表中,这个列表是方法本身的一个字段。也就是说,你可以通过 self.test_foo.patchings
来访问这个列表,然后找到你想要的那个。
不过,当你把 patch()
当作装饰器使用时,start()
和 stop()
实际上并不会被调用,一旦你开始深入修改它,行为就会变得复杂。所以我写了这个上下文管理器。
class unpatch:
def __init__(self, name, method):
compare = patch(name)
self.patcher = next((
p for p in method.patchings
if p.target == compare.getter()
and p.attribute == compare.attribute
), None)
if self.patcher is None:
raise ValueError(name)
def __enter__(self):
self.patcher.__exit__()
def __exit__(self, *exc_info):
self.patcher.__enter__()
在你的测试案例中,你可以这样使用它:
with unpatch('foo.narf', self.test_foo):
foo.narf()
免责声明:这只是一些小技巧。