You are here:  Home » Python » – 入门 – 开发工具(Python教程)(参考资料)

unittest.mock– 入门

版本3.3中的新功能

使用模拟

模拟修补方法

Mock对象的常用用途包括:

  • 修补方法
  • 记录方法调用对象

您可能希望替换对象上的方法以检查系统的另一部分是否使用正确的参数调用它:

>>> real = SomeClass()>>> real.method = MagicMock(name="method")>>> real.method(3, 4, 5, key="value")<MagicMock name="method()" id="...">

一旦我们的模拟被使用了(在这个例子中real.method)它有方法和属性,允许你对它的使用方式做出断言.

注意

在大多数这些例子中,MockMagicMock类是可以互换的。由于MagicMock是更强大的类,它默认使用是合理的.

一旦调用了它的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()

我们不需要做任何工作来为我们的模拟提供’close’方法.Accessing close创建它。因此,如果尚未调用’close’,那么在测试中访问它会创建它,但是assert_called_with()会引发失败异常.

模拟类

一个常见的用例是模拟由测试中的代码实例化的类。当你修补一个类时,那个类被替换为mock。实例由calling the class创建。这意味着您可以通过查看模拟类的返回值来访问“模拟实例”.

在下面的例子中,我们有一个函数some_function实例化Foo并在其上调用一个方法。对patch()的调用用amock替换了类FooFoo实例是调用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"

命名你的模拟

为你的模拟命名是有用的。该名称显示在模拟的repr中,当模拟出现在测试失败消息中时可能会有所帮助。Thename也传播到mock的属性或方法:

>>> mock = MagicMock(name="foo")>>> mock<MagicMock name="foo" id="...">>>> mock.method<MagicMock name="foo.method" id="...">

跟踪所有调用

通常你想跟踪一个方法的多个调用。mock_calls属性记录了模拟的所有callsto子属性 – 以及它们的子项.

>>> mock = MagicMock()>>> mock.method()<MagicMock name="mock.method()" id="...">>>> mock.attribute.method(10, x=53)<MagicMock name="mock.attribute.method()" id="...">>>> 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 == expectedTrue

进行比较的列表的对象但是,不记录返回模拟的调用的参数,这意味着跟踪嵌套调用是不可能的,其中用于创建祖先的参数很重要:

>>> m = Mock()>>> m.factory(important=True).deliver()<Mock name="mock.factory().deliver()" id="...">>>> m.mock_calls[-1] == call.factory(important=False).deliver()True

设置返回值和属性

在模拟对象上设置返回值非常简单:

>>> 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.x = 3>>> mock.x3

有时候你想要模拟更多复杂的情况,例如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 == expectedTrue

这是对.call_list()的调用,将我们的调用对象转换成代表链接调用的调用列表

用模拟增加异常

一个有用的属性是side_effect。如果你将它设置为一个异常类或实例,那么当调用mockis时会引发异常.

>>> mock = Mock(side_effect=Exception("Boom!"))>>> mock()Traceback (most recent call last):  ...Exception: Boom!

侧面效果函数和迭代

side_effect也可以设置为函数或迭代。side_effect作为一个可迭代的用例是你的模拟将被多次调用的地方,并且你希望每个调用返回一个不同的值。当你将side_effect设置为一个iterable时,每次调用mock都会从iterable返回下一个值:

>>> 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

从现有对象创建模拟

过度使用模拟的一个问题是它将测试与模拟的实现相结合而不是真实码。假设你有一个实现some_method。在另一个类的测试中,你提供了一个这个对象的模拟,also提供了some_method。如果lateryou重构了第一类,那么它就不再有了some_method – 即使你的代码现在被破坏了,你的测试仍会继续通过!

Mock允许你提供一个对象作为规范模拟,使用spec关键字参数。访问规范对象上不存在的onock上的方法/属性会立即引发解决方案错误。如果您更改了规范的实现,那么使用该类的测试将立即开始失败而无需在这些测试中实例化.

>>> mock = Mock(spec=SomeClass)>>> mock.old_method()Traceback (most recent call last):   ...AttributeError: object has no attribute "old_method"

使用规范还可以更智能地匹配对weock的调用,无论是否将某些参数作为位置或命名参数传递:

>>> def f(a, b, c): pass...>>> mock = Mock(spec=f)>>> mock(1, 2, 3)<Mock name="mock()" id="140161580456576">>>> mock.assert_called_with(a=1, b=2, c=3)

如果您希望这种更智能的匹配也可以在模拟上使用方法调用,则可以使用自动调整.

如果你想要一个更强大的规范形式,阻止设置任意属性以及获取它们,那么你可以使用spec_set而不是spec.

Patch Decorators

注意

使用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()有两个参数)。将为您创建模拟并通过测试函数/方法:

>>> 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()

当您嵌套修补程序装饰器时,模拟将传递到装饰功能与它们应用的顺序相同(正常Python命令应用了装配器)。这意味着从下到上,所以在上面的例子中为test_module.ClassName2首先传入

//还有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.objectpatch.dict都可以用作上下文管理器.

你使用patch()为你创建一个模拟器,你可以使用with的“as”形式来获取对它们的引用声明:

>>> 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.objectpatch.dict可以用作类装饰器。当以这种方式使用时,它与将名称单独应用于名称以“test”开头的每个方法相同.

更多示例

以下是一些稍微更高级的方案的示例。

模拟链式调用

一旦你理解了return_value属性,模拟链式调用实际上很简单。当第一次调用模拟器,或者在调用它之前取回它return_value时,重新创建Mock .

这意味着您可以通过询问return_value mock

>>> mock = Mock()>>> mock().foo(a=2, b=3)<Mock name="mock().foo()" id="...">>>> mock.return_value.foo.assert_called_with(a=2, b=3)

来查看调用mockedobject后返回的对象是如何使用的。从这里开始,这是一个简单的步骤,然后对链接进行断言调用。当然另一种选择是首先以更稳定的方式编写代码…

所以,假设我们有一些看起来有点像这样的代码:

>>> 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()?具体来说,我们要测试代码部分# morecode以正确的方式使用响应对象.

由于这个调用链是由一个实例属性构成的,我们可以修补它backend Something实例上的//属性。在这种特殊情况下,我们只对最终调用start_call的返回值感兴趣,所以我们没有太多的配置要做。让我们假设它返回的对象是’类似文件’,所以我们将确保我们的响应对象使用内置的open()作为其spec.

为此,我们创建一个模拟实例作为我们的模拟后端,并为它创建一个模拟响应对象。要将响应设置为该最终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)

有了这些,我们将“模拟后端”修改到位并可以进行真实调用:

>>> something.backend = mock_backend>>> something.method()

使用mock_calls我们可以用一个链接检查链接的调用singleassert。链式调用是一行代码中的几个调用,因此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模式包装dateclass,但是通过调用构造函数到realclass(并返回实例).

patch decorator在这里使用tomock out在被测模块中的date类。side_effect然后将模拟日期类上的属性设置为返回实际日期的lambda函数。当调用模拟日期类时,将构造一个真实日期并由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的模块中修补uses。见在哪里修补.

当调用date.today()时会返回一个已知日期,但调用date(...)构造函数仍然返回正常日期。如果没有这个,您可以发现自己必须使用与被测代码完全相同的算法来计算预期结果,这是一个经典的测试反模式.

对日期构造函数的调用记录在mock_date属性(call_count和朋友)中,这对你的测试也很有用.

另一种处理模拟日期或其他内置类的方法在本博客文章中讨论.

模拟生成器方法

Python生成器是一个函数或方法,它使用yield语句在迭代[1]时返回一系列值.

调用生成器方法/函数以返回生成器对象。然后迭代生成器对象。for协议的方法是__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] 还有生成器表达式和生成器的更高级用法,但我们在这里没有关注它们。对发电机的一个非常好的介绍和它们有多么强大:系统程序员的生成器技巧

在每个测试方法中应用相同的补丁

如果你想要多个补丁到位用于多种测试方法明显的方法是将补丁装饰器应用于每个方法。这可能感觉像是不必要的重复。对于Python 2.6或更新版本,您可以使用patch()(以其各种形式)作为类装饰器。这将修补程序应用于类中的所有testmethods。测试方法由名称以test

>>> @patch("mymodule.SomeClass")... class MyTest(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"

开头的方法识别。另一种管理补丁的方法是使用补丁方法:启动和停止。这些允许你将修补程序移动到你的setUptearDown方法

>>> class MyTest(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(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()

模拟未绑定的方法

今天测试我需要修补unbound method(在类而不是实例上修补方法)。我需要将self作为第一个参数传递,因为我想断言哪些对象正在调用这个特定的方法。问题是你无法使用amock进行修补,因为如果用模拟替换未绑定的方法,它不会成为从实例中获取的绑定方法,因此它不会自己传入。解决方法是用realfunction来修补未绑定的方法。patch()装饰器使得如此简单的拓扑方法使用模拟必须创建一个真正的函数变得很麻烦

如果你将autospec=True传递给补丁那么它就会修补用real功能对象。此函数对象具有与替换oneit相同的签名,但委托给模拟器。您仍然可以使用与以前完全相同的方式自动创建自动模式。但这意味着,如果你使用它来修补一个类上的未绑定方法,那么如果从一个实例中获取它,则mockedfunction将被转换为一个绑定方法。它将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.foo_bar.return_value = None>>> mock.foo_bar("baz", spam="eggs")>>> mock.foo_bar.assert_called_with("baz", spam="eggs")

如果你的模拟只被调用一次就可以使用assert_called_once_with()方法也断言call_count是一个

>>> mock.foo_bar.assert_called_once_with("baz", spam="eggs")>>> mock.foo_bar()>>> mock.foo_bar.assert_called_once_with("baz", spam="eggs")Traceback (most recent call last):    ...AssertionError: Expected to be called once. Called 2 times.

// assert_called_withassert_called_once_withmost recent的断言做出断言。如果你的模拟将被多次调用,并且你想对all进行断言你可以使用call_args_list

>>> mock = Mock(return_value=None)>>> mock(1, 2, 3)>>> mock(4, 5, 6)>>> mock()>>> mock.call_args_list[call(1, 2, 3), call(4, 5, 6), call()]

call帮助程序可以很容易地对这些调用进行断言。你可以建立一个预期的呼叫列表,并将其与call_args_list。这看起来非常类似于的reprcall_args_list

>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]>>> mock.call_args_list == expectedTrue

应对可变参数

另一种情况很少见,但是可以咬你,就是当你的mock被调用withmutable参数时。call_argscall_args_list存储references对这些争论。如果参数被测试中的代码变异,那么你可以不再对调用mock时的值进行断言.

这是一些显示问题的示例代码。想象一下’mymodule‘中定义的以下函数:

def frob(val):    passdef grob(val):    "First frob and then clear val"    frob(val)    val.clear()

当我们尝试用正确的参数测试grob调用frob时,看看会发生什么:

>>> with patch("mymodule.frob") as mock_frob:...     val = {6}...     mymodule.grob(val)...>>> valset()>>> mock_frob.assert_called_with({6})Traceback (most recent call last):    ...AssertionError: Expected: (({6},), {})Called with: ((set(),), {})

一种可能性是模拟复制你传入的参数。如果你做了依赖于对象身份的断言,那么这会导致问题.

这是一个使用side_effect功能的解决方案。如果为模拟提供side_effect功能,那么side_effect将使用与模拟相同的args调用。这使我们有机会复制参数并将其存储以供以后断言。在这个例子中我正在使用anothermock来存储参数,以便我可以使用themock方法来执行断言。辅助功能再一次设置好了!

>>> from copy import deepcopy>>> from unittest.mock import Mock, patch, DEFAULT>>> def copy_call_args(mock):...     new_mock = Mock()...     def side_effect(*args, **kwargs):...         args = deepcopy(args)...         kwargs = deepcopy(kwargs)...         new_mock(*args, **kwargs)...         return DEFAULT...     mock.side_effect = side_effect...     return new_mock...>>> with patch("mymodule.frob") as mock_frob:...     new_mock = copy_call_args(mock_frob)...     val = {6}...     mymodule.grob(val)...>>> new_mock.assert_called_with({6})>>> new_mock.call_argscall({6})

copy_call_args使用将被调用的模拟调用。它返回一个我们做断言的newmockside_effect功能制作艺术的副本,并打电话给我们new_mock与副本.

注意

如果你的模拟只会被使用一次,那么在调用它们时,有一种更容易的方法来检查它们。你可以简单地在side_effect函数内进行检查.

>>> def side_effect(arg):...     assert arg == {6}...>>> mock = Mock(side_effect=side_effect)>>> mock({6})>>> mock(set())Traceback (most recent call last):    ...AssertionError

另一种方法是创建MockMagicMock子类,复制(使用copy.deepcopy())参数。这是一个示例实现:

>>> from copy import deepcopy>>> class CopyingMock(MagicMock):...     def __call__(self, *args, **kwargs):...         args = deepcopy(args)...         kwargs = deepcopy(kwargs)...         return super(CopyingMock, self).__call__(*args, **kwargs)...>>> c = CopyingMock(return_value=None)>>> arg = set()>>> c(arg)>>> arg.add(1)>>> c.assert_called_with(set())>>> c.assert_called_with(arg)Traceback (most recent call last):    ...AssertionError: Expected call: mock({1})Actual call: mock(set())>>> c.foo<CopyingMock name="mock.foo" id="...">

当你子类Mock 要么 MagicMock所有动态创建的属性,return_value将自动使用您的子类。这意味着CopyingMock的所有孩子也会有类型CopyingMock.

嵌套补丁

使用补丁作为上下文管理器是很好的,但是如果你做了多个补丁,你最终可以嵌套语句进一步缩进,以便进一步改进:

>>> class MyTest(TestCase):......     def test_foo(self):...         with patch("mymodule.Foo") as mock_foo:...             with patch("mymodule.Bar") as mock_bar:...                 with patch("mymodule.Spam") as mock_spam:...                     assert mymodule.Foo is mock_foo...                     assert mymodule.Bar is mock_bar...                     assert mymodule.Spam is mock_spam...>>> original = mymodule.Foo>>> MyTest("test_foo").test_foo()>>> assert mymodule.Foo is original

使用unittest cleanup函数和补丁方法:启动和停止我们可以在没有嵌套缩进的情况下实现相同的效果。一个简单的帮助方法,create_patch,将补丁放到位并返回创建的模拟器:

>>> class MyTest(TestCase):......     def create_patch(self, name):...         patcher = patch(name)...         thing = patcher.start()...         self.addCleanup(patcher.stop)...         return thing......     def test_foo(self):...         mock_foo = self.create_patch("mymodule.Foo")...         mock_bar = self.create_patch("mymodule.Bar")...         mock_spam = self.create_patch("mymodule.Spam")......         assert mymodule.Foo is mock_foo...         assert mymodule.Bar is mock_bar...         assert mymodule.Spam is mock_spam...>>> original = mymodule.Foo>>> MyTest("test_foo").run()>>> assert mymodule.Foo is original

MagicMock模拟字典

你可能想要模拟一个字典或其他容器对象,记录它的同时仍然表现得像字典.

我们可以用MagicMock,它将表现得像一个字典,并使用side_effect委托字典访问我们控制下的真正的字典.

__getitem__()__setitem__()我们的方法MagicMock被调用(普通字典访问)然后用键调用side_effect(在__setitem__的情况下也是这个值)。我们也可以控制返回的东西.

MagicMock已经使用了我们可以使用像call_args_list这样的属性断言字典的使用方式:

>>> my_dict = {"a": 1, "b": 2, "c": 3}>>> def getitem(name):...      return my_dict[name]...>>> def setitem(name, val):...     my_dict[name] = val...>>> mock = MagicMock()>>> mock.__getitem__.side_effect = getitem>>> mock.__setitem__.side_effect = setitem

注意

使用MagicMock的另一种方法是使用Mockonly提供你特别想要的魔术方法:

>>> mock = Mock()>>> mock.__getitem__ = Mock(side_effect=getitem)>>> mock.__setitem__ = Mock(side_effect=setitem)

一个third选项是使用MagicMock而是传入dict作为spec(或spec_set)争论到MagicMock只创建了hasdictionary魔术方法:

>>> mock = MagicMock(spec_set=dict)>>> mock.__getitem__.side_effect = getitem>>> mock.__setitem__.side_effect = setitem

有了这些副作用函数,mock的行为就像一个normaldictionary,但记录了访问权限。它甚至提出了KeyError如果你试图访问一个不存在的密钥.

>>> mock["a"]1>>> mock["c"]3>>> mock["d"]Traceback (most recent call last):    ...KeyError: "d">>> mock["b"] = "fish">>> mock["d"] = "eggs">>> mock["b"]"fish">>> mock["d"]"eggs"

使用它之后你可以使用普通的模拟方法和属性对访问进行断言:

>>> mock.__getitem__.call_args_list[call("a"), call("c"), call("d"), call("b"), call("d")]>>> mock.__setitem__.call_args_list[call("b", "fish"), call("d", "eggs")]>>> my_dict{"a": 1, "c": 3, "b": "fish", "d": "eggs"}

模拟子类及其属性

为什么你可能想要子类化Mock有多种原因。一个原因可能是添加辅助方法。这是一个愚蠢的例子:

>>> class MyMock(MagicMock):...     def has_been_called(self):...         return self.called...>>> mymock = MyMock(return_value=None)>>> mymock<MyMock id="...">>>> mymock.has_been_called()False>>> mymock()>>> mymock.has_been_called()True

的标准行为Mock实例是属性和返回值模拟与访问模型的类型相同。这确保了Mock属性是MocksMagicMock属性是MagicMocks [2]。因此,如果你是子类化添加辅助​​方法,那么它们也可以在属性和返回值模拟yoursubclass的实例上使用.

>>> mymock.foo<MyMock name="mock.foo" id="...">>>> mymock.foo.has_been_called()False>>> mymock.foo()<MyMock name="mock.foo()" id="...">>>> mymock.foo.has_been_called()True

有时这很不方便。例如,一个用户正在子类mock tocreated一个Twisted adaptor。这个应用于属性实际上也会导致错误.

Mock(各种风格)使用一种叫做_get_child_mock为属性和返回值创建这些“子模拟”。您可以通过重写此方法来阻止将子类用于属性。签名是它需要任意关键字参数(**kwargs)然后通过模拟构造函数传递:

>>> class Subclass(MagicMock):...     def _get_child_mock(self, **kwargs):...         return MagicMock(**kwargs)...>>> mymock = Subclass()>>> mymock.foo<MagicMock name="mock.foo" id="...">>>> assert isinstance(mymock, Subclass)>>> assert not isinstance(mymock.foo, Subclass)>>> assert not isinstance(mymock(), Subclass)
[2] 此规则的一个例外是不可调用的模拟。属性使用可调用的变体,因为否则不可调用的模拟不能具有callablemethods.

使用patch.dict进行模拟导入

模拟可能很难的一种情况是你有一个本地导入函数。这些更难以模拟,因为他们没有使用我们可以修补的模块命名空间中的对象.

通常应避免当地进口。它们有时是为防止圆形依赖而做的,有usually一种更好的方法来解决问题(重构代码)或通过延迟进口来防止“前期成本”。这也可以通过比无条件localimport更好的方式解决(将模块存储为类或模块属性,并且仅在首次使用时进行导入).

除此之外,有一种方法可以使用mock来影响导入的结果。导入从object字典中获取sys.modules。请注意它有一个object,不一定是一个模块。首次导入模块导致模块对象被放入sys.modules,所以通常当你导入一些东西时,你会得到一个模块。这不一定是这种情况.

这意味着你可以使用patch.dict()temporarily把嘲笑放在sys.modules。当这个补丁处于活动状态时,任何导入都将获取模拟。当补丁完成时(装饰函数退出,with statementbody完成或patcher.stop()被称为)那么以前的任何东西都会安全地恢复.

这是一个模拟’fooble’模块的例子.

>>> mock = Mock()>>> with patch.dict("sys.modules", {"fooble": mock}):...    import fooble...    fooble.blob()...<Mock name="mock.blob()" id="...">>>> assert "fooble" not in sys.modules>>> mock.blob.assert_called_once_with()

你可以看到import fooble成功,但在退出时,sys.modules.

中没有’fooble’left这也适用于from module import name form:

>>> mock = Mock()>>> with patch.dict("sys.modules", {"fooble": mock}):...    from fooble import blob...    blob.blip()...<Mock name="mock.blob.blip()" id="...">>>> mock.blob.blip.assert_called_once_with()

稍微多做一些工作,你也可以模拟包导入:

>>> mock = Mock()>>> modules = {"package": mock, "package.module": mock.module}>>> with patch.dict("sys.modules", modules):...    from package.module import fooble...    fooble()...<Mock name="mock.module.fooble()" id="...">>>> mock.module.fooble.assert_called_once_with()

跟踪调用的顺序和较少的详细调用断言

Mock类允许你通过order跟踪你的模拟对象的方法调用的method_calls属性。这不允许你跟踪单独的模拟对象之间的调用顺序,但是我们可以使用mock_calls达到同样的效果

因为嘲笑在mock_calls并且访问模拟的anadbitrary属性会创建一个子模拟,我们可以从父模型创建我们的单独模型。然后将所有儿童模拟的召唤按顺序记录在mock_calls父母:

>>> manager = Mock()>>> mock_foo = manager.foo>>> mock_bar = manager.bar
>>> mock_foo.something()<Mock name="mock.foo.something()" id="...">>>> mock_bar.other.thing()<Mock name="mock.bar.other.thing()" id="...">
>>> manager.mock_calls[call.foo.something(), call.bar.other.thing()]

然后我们可以通过与管理器上的mock_calls属性进行比较来断言调用,包括命令:

>>> expected_calls = [call.foo.something(), call.bar.other.thing()]>>> manager.mock_calls == expected_callsTrue

如果patch正在创建并放置你的模拟,然后你可以使用attach_mock()方法将它们附加到管理器模拟。后来的电话将记录在经理的mock_calls中.

>>> manager = MagicMock()>>> with patch("mymodule.Class1") as MockClass1:...     with patch("mymodule.Class2") as MockClass2:...         manager.attach_mock(MockClass1, "MockClass1")...         manager.attach_mock(MockClass2, "MockClass2")...         MockClass1().foo()...         MockClass2().bar()...<MagicMock name="mock.MockClass1().foo()" id="..."><MagicMock name="mock.MockClass2().bar()" id="...">>>> manager.mock_calls[call.MockClass1(), call.MockClass1().foo(), call.MockClass2(), call.MockClass2().bar()]

如果已经进行了很多调用,但是你只对它们的特定顺序感兴趣,那么另一种方法是使用assert_has_calls()方法。这需要一个电话列表(用call宾语)。如果这个调用序列在mock_calls中,那么断言成功.

>>> m = MagicMock()>>> m().foo().bar().baz()<MagicMock name="mock().foo().bar().baz()" id="...">>>> m.one().two().three()<MagicMock name="mock.one().two().three()" id="...">>>> calls = call.one().two().three().call_list()>>> m.assert_has_calls(calls)

即使链接的调用m.one().two().three()不是对模拟的唯一调用,断言仍然成功

有时一个模拟可能会有多次调用,而你只是对这些调用的some断言感兴趣。你可能甚至不关心这个订单。在这种情况下,您可以将any_order=True传递给assert_has_calls

>>> m = MagicMock()>>> m(1), m.two(2, 3), m.seven(7), m.fifty("50")(...)>>> calls = [call.fifty("50"), call(1), call.seven(7)]>>> m.assert_has_calls(calls, any_order=True)

更复杂的参数匹配

使用与ANY相同的基本概念,我们可以实现匹配器对用作mock的参数的对象执行更复杂的断言.

假设我们希望将一些对象传递给一个模拟,默认情况下,根据对象标识(这是用户定义类的Python默认值)相等。使用assert_called_with()我们需要传递完全相同的对象。如果我们只对这个对象的某些属性感兴趣,那么我们可以创建一个匹配器,为我们检查这些属性.

你可以在这个例子中看到对assert_called_with的’标准’调用是如何有效的:

>>> class Foo:...     def __init__(self, a, b):...         self.a, self.b = a, b...>>> mock = Mock(return_value=None)>>> mock(Foo(1, 2))>>> mock.assert_called_with(Foo(1, 2))Traceback (most recent call last):    ...AssertionError: Expected: call(<__main__.Foo object at 0x...>)Actual call: call(<__main__.Foo object at 0x...>)

我们的Foo类的比较函数可能看起来像这样:

>>> def compare(self, other):...     if not type(self) == type(other):...         return False...     if self.a != other.a:...         return False...     if self.b != other.b:...         return False...     return True...

并且可以使用这样的比较函数来进行等式运算的匹配器对象看起来像这样:

>>> class Matcher:...     def __init__(self, compare, some_obj):...         self.compare = compare...         self.some_obj = some_obj...     def __eq__(self, other):...         return self.compare(self.some_obj, other)...

把所有这些放在一起:

>>> match_foo = Matcher(compare, Foo(1, 2))>>> mock.assert_called_with(match_foo)

Matcher用我们的比较函数和Foo我们想要比较对象。在assert_called_with/Matcher将调用equalitymethod,它将模拟所称的对象与我们创建匹配器的对象进行比较。如果它们匹配则assert_called_with通过,如果它们没有,则AssertionError被抬起:

>>> match_wrong = Matcher(compare, Foo(3, 4))>>> mock.assert_called_with(match_wrong)Traceback (most recent call last):    ...AssertionError: Expected: ((<Matcher object at 0x...>,), {})Called with: ((<Foo object at 0x...>,), {})

通过一些调整你可以让比较函数直接提升AssertionError并提供更有用的失败信息.

从版本1.5开始,Python测试库PyHamcrest以其相等匹配器(hamcrest.library.integration.match_equality)的形式提供了类似的功能,在这里可能很有用.

评论被关闭。