3.3 新版功能.
我们提供的服务有:做网站、成都网站建设、微信公众号开发、网站优化、网站认证、水城ssl等。为超过千家企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的水城网站制作公司
使用 Mock 的常见场景:
模拟函数调用
记录在对象上的方法调用
你可能需要替换一个对象上的方法,用于确认此方法被系统中的其他部分调用过,并且调用时使用了正确的参数。
>>> real = SomeClass()
>>> real.method = MagicMock(name='method')
>>> real.method(3, 4, 5, key='value')
使用了 mock (本例中的 real.method
) 之后,它有方法和属性可以让你针对它是被如何使用的下断言。
备注
在多数示例中,Mock 与 MagicMock 两个类可以相互替换,而 MagicMock
是一个更适用的类,通常情况下,使用它就可以了。
如果 mock 被调用,它的 called 属性就会变成 True
,更重要的是,我们可以使用 assert_called_with() 或者 assert_called_once_with() 方法来确认它在被调用时使用了正确的参数。
在如下的测试示例中,验证对于 ProductionClass().method
的调用会导致 something
的调用。
>>> class ProductionClass:
... def method(self):
... self.something(1, 2, 3)
... def something(self, a, b, c):
... pass
...
>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3)
上一个例子中我们直接在对象上给方法打补丁以检查它是否被正确地调用。 另一个常见的用例是将一个对象传给一个方法(或被测试系统的某个部分)然后检查它是否以正确的方式被使用。
下面这个简单的 ProductionClass
具有一个 closer
方法。 如果它附带一个对象被调用那么它就会调用其中的 close
。
>>> class ProductionClass:
... def closer(self, something):
... something.close()
...
所以为了测试它我们需要传入一个带有 close
方法的对象并检查它是否被正确地调用。
>>> real = ProductionClass()
>>> mock = Mock()
>>> real.closer(mock)
>>> mock.close.assert_called_with()
我们不需要做任何事来在我们的 mock 上提供 ‘close’ 方法。 访问 close 的操作就会创建它。 因此,如果 ‘close’ 还未被调用那么在测试时访问它就将创建它,但是 assert_called_with() 则会引发一个失败的异常。
一个常见的用例是模拟被测试的代码所实例化的类。 当你给一个类打上补丁,该类就会被替换为一个 mock。 实例是通过 该用该类 来创建的。 这意味着你要通过查看被模拟类的返回值来访问 “mock 实例”。
在下面的例子中我们有一个函数 some_function
实例化了 Foo
并调用该实例中的一个方法。 对 patch() 的调用会将类 Foo
替换为一个 mock。 Foo
实例是调用该 mock 的结果,所以它是通过修改 return_value 来配置的。
>>> def some_function():
... instance = module.Foo()
... return instance.method()
...
>>> with patch('module.Foo') as mock:
... instance = mock.return_value
... instance.method.return_value = 'the result'
... result = some_function()
... assert result == 'the result'
给你的 mock 起个名字可能会很有用。 名字会显示在 mock 的 repr 中并在 mock 出现于测试失败消息中时可以帮助理解。 这个名字也会被传播给 mock 的属性或方法:
>>> mock = MagicMock(name='foo')
>>> mock
>>> mock.method
通常你会想要追踪对某个方法的多次调用。 mock_calls 属性记录了所有对 mock 的子属性的调用 —— 并且还包括对它们的子属性的调用。
>>> mock = MagicMock()
>>> mock.method()
>>> mock.attribute.method(10, x=53)
>>> mock.mock_calls
[call.method(), call.attribute.method(10, x=53)]
如果你做了一个有关 mock_calls
的断言并且有任何非预期的方法被调用,则断言将失败。 这很有用处,因为除了断言你所预期的调用已被执行,你还会检查它们是否以正确的顺序被执行并且没有额外的调用:
你使用 call 对象来构造列表以便与 mock_calls
进行比较:
>>> expected = [call.method(), call.attribute.method(10, x=53)]
>>> mock.mock_calls == expected
True
然而,返回 mock 的调用的形参不会被记录,这意味着不可能追踪附带了重要形参的创建上级对象的嵌套调用:
>>> m = Mock()
>>> m.factory(important=True).deliver()
>>> m.mock_calls[-1] == call.factory(important=False).deliver()
True
在 mock 对象上设置返回值是非常容易的:
>>> mock = Mock()
>>> mock.return_value = 3
>>> mock()
3
当然你也可以对 mock 上的方法做同样的操作:
>>> mock = Mock()
>>> mock.method.return_value = 3
>>> mock.method()
3
返回值也可以在构造器中设置:
>>> mock = Mock(return_value=3)
>>> mock()
3
如果你需要在你的 mock 上设置一个属性,只需这样做:
>>> mock = Mock()
>>> mock.x = 3
>>> mock.x
3
有时你会想要模拟更复杂的情况,例如这个例子 mock.connection.cursor().execute("SELECT 1")
。 如果我们希望这个调用返回一个列表,那么我们还必须配置嵌套调用的结果。
我们可以像这样使用 call 在一个“链式调用”中构造调用集合以便随后方便地设置断言:
>>> mock = Mock()
>>> cursor = mock.connection.cursor.return_value
>>> cursor.execute.return_value = ['foo']
>>> mock.connection.cursor().execute("SELECT 1")
['foo']
>>> expected = call.connection.cursor().execute("SELECT 1").call_list()
>>> mock.mock_calls
[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
>>> mock.mock_calls == expected
True
对 .call_list()
的调用会将我们的调用对象转成一个代表链式调用的调用列表。
一个很有用的属性是 side_effect。 如果你将该属性设为一个异常类或者实例那么当 mock 被调用时该异常将会被引发。
>>> mock = Mock(side_effect=Exception('Boom!'))
>>> mock()
Traceback (most recent call last):
...
Exception: Boom!
side_effect
也可以被设为一个函数或可迭代对象。 side_effect
作为可迭代对象的应用场景适用于你的 mock 将要被多次调用,并且你希望每次调用都返回不同的值的情况。 当你将 side_effect
设为一个可迭代对象时每次对 mock 的调用将返回可迭代对象的下一个值。
>>> mock = MagicMock(side_effect=[4, 5, 6])
>>> mock()
4
>>> mock()
5
>>> mock()
6
对于更高级的用例,例如根据 mock 调用时附带的参数动态改变返回值,side_effect
可以指定一个函数。 该函数将附带与 mock 相同的参数被调用。 该函数所返回的就是调用所返回的对象:
>>> vals = {(1, 2): 1, (2, 3): 2}
>>> def side_effect(*args):
... return vals[args]
...
>>> mock = MagicMock(side_effect=side_effect)
>>> mock(1, 2)
1
>>> mock(2, 3)
2
从 python 3.8 起,AsyncMock
和 MagicMock
支持通过 __aiter__
来模拟 异步迭代器。 __aiter__
的 return_value 属性可以被用来设置要用于迭代的返回值。
>>> mock = MagicMock() # AsyncMock also works here
>>> mock.__aiter__.return_value = [1, 2, 3]
>>> async def main():
... return [i async for i in mock]
...
>>> asyncio.run(main())
[1, 2, 3]
从 Python 3.8 起,AsyncMock
和 MagicMock
支持通过 __aenter__
和 __aexit__
来模拟 异步上下文管理器。 在默认情况下,__aenter__
和 __aexit__
将为返回异步函数的 AsyncMock
实例。
>>> class AsyncContextManager:
... async def __aenter__(self):
... return self
... async def __aexit__(self, exc_type, exc, tb):
... pass
...
>>> mock_instance = MagicMock(AsyncContextManager()) # AsyncMock also works here
>>> async def main():
... async with mock_instance as result:
... pass
...
>>> asyncio.run(main())
>>> mock_instance.__aenter__.assert_awaited_once()
>>> mock_instance.__aexit__.assert_awaited_once()
使用模拟操作的一个问题是它会将你的测试与你的 mock 实现相关联而不是与你的真实代码相关联。 假设你有一个实现了 some_method
的类。 在对另一个类的测试中,你提供了一个 同样 提供了 some_method
的模拟该对象的 mock 对象。 如果后来你重构了第一个类,使得它不再具有 some_method
—— 那么你的测试将继续保持通过,尽管现在你的代码已经被破坏了!
Mock 允许你使用allows you to provide an object as a specification for the mock, using the spec 关键字参数来提供一个对象作为 mock 的规格说明。 在 mock 上访问不存在于你的规格说明对象中的方法 / 属性将立即引发一个属性错误。 如果你修改你的规格说明的实现,,那么使用了该类的测试将立即开始失败而不需要你在这些测试中实例化该类。
>>> mock = Mock(spec=SomeClass)
>>> mock.old_method()
Traceback (most recent call last):
...
AttributeError: object has no attribute 'old_method'
使用规格说明还可以启用对 mock 的调用的更聪明的匹配操作,无论是否有将某些形参作为位置或关键字参数传入:
>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)
如果你想要让这些更聪明的匹配操作也适用于 mock 上的方法调用,你可以使用 auto-speccing。
如果你想要更强形式的规格说明以防止设置任意属性并获取它们那么你可以使用 spec_set 来代替 spec。
备注
在查找对象的名称空间中修补对象使用 patch() 。使用起来很简单,阅读 补丁的位置 来快速上手。
测试中的一个常见需求是为类属性或模块属性打补丁,例如修补内置对象或修补某个模块中的类来测试其是否被实例化。 模块和类都可算是全局对象,因此对它们打补丁的操作必须在测试完成之后被还原否则补丁将持续影响其他测试并导致难以诊断的问题。
为此 mock 提供了三个便捷的装饰器: patch(), patch.object() 和 patch.dict()。 patch
接受单个字符串,其形式 package.module.Class.attribute
指明你要修补的属性。 它还可选择接受一个值用来替换指定的属性(或者类对象等等)。 ‘patch.object’ 接受一个对象和你想要修补的属性名称,并可选择接受要用作补丁的值。
patch.object
:
>>> original = SomeClass.attribute
>>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
... def test():
... assert SomeClass.attribute == sentinel.attribute
...
>>> test()
>>> assert SomeClass.attribute == original
>>> @patch('package.module.attribute', sentinel.attribute)
... def test():
... from package.module import attribute
... assert attribute is sentinel.attribute
...
>>> test()
如果你要给一个模块 (包括 builtins) 打补丁则可使用 patch() 来代替 patch.object():
>>> mock = MagicMock(return_value=sentinel.file_handle)
>>> with patch('builtins.open', mock):
... handle = open('filename', 'r')
...
>>> mock.assert_called_with('filename', 'r')
>>> assert handle == sentinel.file_handle, "incorrect file handle returned"
如有必要模块名可以是“带点号”的,其形式如 package.module
:
>>> @patch('package.module.ClassName.attribute', sentinel.attribute)
... def test():
... from package.module import ClassName
... assert ClassName.attribute == sentinel.attribute
...
>>> test()
一个良好的模式是实际地装饰测试方法本身:
>>> class MyTest(unittest.TestCase):
... @patch.object(SomeClass, 'attribute', sentinel.attribute)
... def test_something(self):
... self.assertEqual(SomeClass.attribute, sentinel.attribute)
...
>>> original = SomeClass.attribute
>>> MyTest('test_something').test_something()
>>> assert SomeClass.attribute == original
如果你想要通过 Mock 来打补丁,你可以只附带一个参数使用 patch() (或附带两个参数使用 patch.object())。 这将为你创建 mock 并传递给测试函数 / 方法:
>>> class MyTest(unittest.TestCase):
... @patch.object(SomeClass, 'static_method')
... def test_something(self, mock_method):
... SomeClass.static_method()
... mock_method.assert_called_with()
...
>>> MyTest('test_something').test_something()
你可以使用以下模式来堆叠多个补丁装饰器:
>>> class MyTest(unittest.TestCase):
... @patch('package.module.ClassName1')
... @patch('package.module.ClassName2')
... def test_something(self, MockClass2, MockClass1):
... self.assertIs(package.module.ClassName1, MockClass1)
... self.assertIs(package.module.ClassName2, MockClass2)
...
>>> MyTest('test_something').test_something()
当你嵌套 patch 装饰器时将以它们被应用的相同顺序(即 Python 应用装饰器的正常顺序)将 mock 传入被装饰的函数。 也就是说从下往上,因此在上面的示例中 test_module.ClassName2
的 mock 会被最先传入。
还有一个 patch.dict() 用于在一定范围内设置字典中的值,并在测试结束时将字典恢复为其原始状态:
>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
... assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original
patch
, patch.object
和 patch.dict
都可被用作上下文管理器。
在你使用 patch() 为你创建 mock 时,你可以使用 with 语句的 “as” 形式来获得对 mock 的引用:
>>> class ProductionClass:
... def method(self):
... pass
...
>>> with patch.object(ProductionClass, 'method') as mock_method:
... mock_method.return_value = None
... real = ProductionClass()
... real.method(1, 2, 3)
...
>>> mock_method.assert_called_with(1, 2, 3)
作为替代 patch
, patch.object
和 patch.dict
可以被用作类装饰器。 当以此方式使用时其效果与将装饰器单独应用到每个以 “test” 打头的方法上相同。
下面是一些针对更为高级应用场景的补充示例。
实际上一旦你理解了 return_value 属性那么使用 mock 模拟链式调用就会相当直观。 当一个 mock 首次被调用,或者当你在它被调用前获取其 return_value
时,将会创建一个新的 Mock。
这意味着你可以通过检视 return_value
mock 来了解从调用被模拟对象返回的对象是如何被使用的:
>>> mock = Mock()
>>> mock().foo(a=2, b=3)
>>> mock.return_value.foo.assert_called_with(a=2, b=3)
从这里开始只需一个步骤即可配置并创建有关链式调用的断言。 当然还有另一种选择是首先以更易于测试的方式来编写你的代码…
因此,如果我们有这样一些代码:
>>> class Something:
... def __init__(self):
... self.backend = BackendProvider()
... def method(self):
... response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
... # more code
假定 BackendProvider
已经过良好测试,我们要如何测试 method()
? 特别地,我们希望测试代码段 # more code
是否以正确的方式使用了响应对象。
由于这个链式调用来自一个实例属性我们可以对 backend
属性在 Something
实例上进行猴子式修补。 在这个特定情况下我们只对最后调用 start_call
的返回值感兴趣所以我们不需要进行太多的配置。 让我们假定它返回的是“文件类”对象,因此我们将确保我们的响应对象使用内置的 open() 作为其 spec
。
为了做到这一点我们创建一个 mock 实例作为我们的 mock 后端并为它创建一个 mock 响应对象。 要将该响应对象设为最后的 start_call
的返回值我们可以这样做:
mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response
我们可以通过更好一些的方式做到这一点,即使用 configure_mock() 方法直接为我们设置返回值:
>>> something = Something()
>>> mock_response = Mock(spec=open)
>>> mock_backend = Mock()
>>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
>>> mock_backend.configure_mock(**config)
有了这些我们就能准备好给“mock 后端”打上猴子补丁并可以执行真正的调用:
>>> something.backend = mock_backend
>>> something.method()
使用 mock_calls 我们可以通过一个断言来检查链式调用。 一个链式调用就是在一行代码中连续执行多个调用,所以在 mock_calls
中将会有多个条目。 我们可以使用 call.call_list() 来为我们创建这个调用列表:
>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
>>> call_list = chained.call_list()
>>> assert mock_backend.mock_calls == call_list
在某些测试中我希望模拟对 datetime.date.today() 的调用以返回一个已知的日期,但我又不想阻止被测试的代码创建新的日期对象。 很不幸 datetime.date 是用 C 语言编写的,因此我不能简单地给静态的 date.today()
方法打上猴子补丁。
我找到了实现这一点的简单方式即通过一个 mock 来实际包装日期类,但通过对构造器的调用传递给真实的类(并返回真实的实例)。
这里使用 patch 装饰器 来模拟被测试模块中的 date
类。 模拟 date 类中的 side_effect
属性随后被设为一个返回真实日期的 lambda 函数。 当模拟 date 类被调用时将由 side_effect
构造并返回一个真实日期。
>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
... mock_date.today.return_value = date(2010, 10, 8)
... mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
... assert mymodule.date.today() == date(2010, 10, 8)
... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
请注意我们没有在全局范围上修补 datetime.date,我们只是在 使用 它的模块中给 date
打补丁。 参见 补丁的位置。
当 date.today()
被调用时将返回一个已知的日期,但对 date(...)
构造器的调用仍会返回普通的日期。 如果不是这样你会发现你必须使用与被测试的代码完全相同的算法来计算出预期的结果,这是测试工作中的一个经典的反模式。
对 date 构造器的调用会被记录在 mock_date
属性中 (call_count
等),它们也可能对你的测试有用处。
有关处理模块日期或其他内置类的一种替代方式的讨论请参见 这篇博客文章。
Python 生成器是指在被迭代时使用 yield 语句来返回一系列值的函数或方法 1。
调用生成器方法 / 函数将返回生成器对象。 生成器对象随后会被迭代。 迭代操作对应的协议方法是 __iter__(),因此我们可以使用 MagicMock 来模拟它。
以下是一个使用 “iter” 方法模拟为生成器的示例类:
>>> class Foo:
... def iter(self):
... for i in [1, 2, 3]:
... yield i
...
>>> foo = Foo()
>>> list(foo.iter())
[1, 2, 3]
我们要如何模拟这个类,特别是它的 “iter” 方法呢?
为了配置从迭代操作(隐含在对 list 的调用中)返回的值,我们需要配置调用 foo.iter()
所返回的对象。
>>> mock_foo = MagicMock()
>>> mock_foo.iter.return_value = iter([1, 2, 3])
>>> list(mock_foo.iter())
[1, 2, 3]
1
此外还有生成器表达式和更多的生成器 进阶用法,但在这里我们不去关心它们。 有关生成器及其强大功能的一个很好的介绍请参阅: 针对系统程序员的生成器妙招。
If you want several patches in place for multiple test methods the obvious way is to apply the patch decorators to every method. This can feel like unnecessary repetition. Instead, you can use patch() (in all its various forms) as a class decorator. This applies the patches to all test methods on the class. A test method is identified by methods whose names start with test
:
>>> @patch('mymodule.SomeClass')
... class MyTest(unittest.TestCase):
...
... def test_one(self, MockSomeClass):
... self.assertIs(mymodule.SomeClass, MockSomeClass)
...
... def test_two(self, MockSomeClass):
... self.assertIs(mymodule.SomeClass, MockSomeClass)
...
... def not_a_test(self):
... return 'something'
...
>>> MyTest('test_one').test_one()
>>> MyTest('test_two').test_two()
>>> MyTest('test_two').not_a_test()
'something'
另一种管理补丁的方式是使用 补丁方法: start 和 stop。 它允许你将打补丁操作移至你的 setUp
和 tearDown
方法中。
>>> class MyTest(unittest.TestCase):
... def setUp(self):
... self.patcher = patch('mymodule.foo')
... self.mock_foo = self.patcher.start()
...
... def test_foo(self):
... self.assertIs(mymodule.foo, self.mock_foo)
...
... def tearDown(self):
... self.patcher.stop()
...
>>> MyTest('test_foo').run()
如果你要使用这个技巧则你必须通过调用 stop
来确保补丁被“恢复”。 这可能要比你想像的更麻烦,因为如果在 setUp 中引发了异常那么 tearDown 将不会被调用。 unittest.TestCase.addCleanup() 可以做到更方便:
>>> class MyTest(unittest.TestCase):
... def setUp(self):
... patcher = patch('mymodule.foo')
... self.addCleanup(patcher.stop)
... self.mock_foo = patcher.start()
...
... def test_foo(self):
... self.assertIs(mymodule.foo, self.mock_foo)
...
>>> MyTest('test_foo').run()
当前在编写测试时我需要修补一个 未绑定方法 (在类上而不是在实例上为方法打补丁)。 我需要将 self 作为第一个参数传入因为我想对哪些对象在调用这个特定方法进行断言。 问题是这里你不能用 mock 来打补丁,因为如果你用 mock 来替换一个未绑定方法那么当从实例中获取时它就不会成为一个已绑定方法,因而它不会获得传入的 self。 绕过此问题的办法是改用一个真正的函数来修补未绑定方法。 patch() 装饰器让使用 mock 来给方法打补丁变得如此简单以至于创建一个真正的函数成为一件麻烦事。
如果将 autospec=True
传给 patch 那么它就会用一个 真正的 函数对象来打补丁。 这个函数对象具有与它所替换的函数相同的签名,但会在内部将操作委托给一个 mock。 你仍然可以通过与以前完全相同的方式来自动创建你的 mock。 但是这将意味着一件事,就是如果你用它来修补一个类上的非绑定方法那么如果它是从一个实例中获取则被模拟的函数将被转为已绑定方法。 传给它的第一个参数将为 self
,而这真是我想要的:
>>> class Foo:
... def foo(self):
... pass
...
>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
... mock_foo.return_value = 'foo'
... foo = Foo()
... foo.foo()
...
'foo'
>>> mock_foo.assert_called_once_with(foo)
如果我们不使用 autospec=True
那么这个未绑定方法会改为通过一个 Mock 补丁来修补,而不是附带 self
来调用。
mock 有一个很好的 API 用于针对你的 mock 对象如何被使用来下断言。
>>> mock = Mock()
>>> mock.foo_bar.
当前题目:创新互联Python教程:unittest.mock —- 上手指南
URL分享:http://www.36103.cn/qtweb/news28/12928.html网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联