陷阱:装饰顺序
  当我们在测试方法中使用多个装饰器,其顺序是很重要的,并且很容易混乱。基本上,当装饰器被映射到方法参数时, 装饰器的工作顺序是反向的 。思考这个例子:
  @mock.patch('mymodule.sys')
  @mock.patch('mymodule.os')
  @mock.patch('mymodule.os.path')
  def test_something(self, mock_os_path, mock_os, mock_sys):
  pass
  注意到我们的参数和装饰器的顺序是反向匹配了吗?这部分是由 Python 的工作方式 所导致的。这里是使用多个装饰器的情况下它们执行顺序的伪代码:
  patch_sys(patch_os(patch_os_path(test_something)))
  因为 sys 补丁位于外层,所以它晚执行,使得它成为实际测试方法参数的后一个参数。请特别注意这一点,并且在运行你的测试用例时,使用调试器来保证正确的参数以正确的顺序注入。
  方法 2:创建 Mock 实例
  我们可以使用构造函数为 UploadService 提供一个 Mock 实例,而不是模拟特定的实例方法。我更推荐方法 1,因为它更加精确,但在多数情况,方法 2 或许更加有效和必要。让我们再次重构测试用例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import RemovalService, UploadService
import mock
import unittest
class RemovalServiceTestCase(unittest.TestCase):
@mock.patch('mymodule.os.path')
@mock.patch('mymodule.os')
def test_rm(self, mock_os, mock_path):
# instantiate our service
reference = RemovalService()
# set up the mock
mock_path.isfile.return_value = False
reference.rm("any path")
# test that the remove call was NOT called.
self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
# make the file 'exist'
mock_path.isfile.return_value = True
reference.rm("any path")
mock_os.remove.assert_called_with("any path")
class UploadServiceTestCase(unittest.TestCase):
def test_upload_complete(self, mock_rm):
# build our dependencies
mock_removal_service = mock.create_autospec(RemovalService)
reference = UploadService(mock_removal_service)
# call upload_complete, which should, in turn, call `rm`:
reference.upload_complete("my uploaded file")
# test that it called the rm method
mock_removal_service.rm.assert_called_with("my uploaded file")
  在这个例子中,我们甚至不需要修补任何功能,只需为 RemovalService 类创建一个 auto-spec,然后将实例注入到我们的 UploadService 以验证功能。
  mock.create_autospec 方法为类提供了一个同等功能实例。实际上来说,这意味着在使用返回的实例进行交互的时候,如果使用了非法的方式将会引发异常。更具体地说,如果一个方法被调用时的参数数目不正确,将引发一个异常。这对于重构来说是非常重要。当一个库发生变化的时候,中断测试正是所期望的。如果不使用 auto-spec,尽管底层的实现已经被破坏,我们的测试仍然会通过。
  陷阱:mock.Mock 和 mock.MagicMock 类
  mock 库包含了两个重要的类 mock.Mock 和 mock.MagicMock ,大多数内部函数都是建立在这两个类之上的。当在选择使用 mock.Mock 实例、 mock.MagicMock 实例还是 auto-spec 的时候,通常倾向于选择使用 auto-spec,因为对于未来的变化,它更能保持测试的健全。这是因为 mock.Mock 和 mock.MagicMock 会无视底层的 API,接受所有的方法调用和属性赋值。比如下面这个用例:
  class Target(object):
  def apply(value):
  return value
  def method(target, value):
  return target.apply(value)
  我们可以像下面这样使用 mock.Mock 实例进行测试:
  class MethodTestCase(unittest.TestCase):
  def test_method(self):
  target = mock.Mock()
  method(target, "value")
  target.apply.assert_called_with("value")
  这个逻辑看似合理,但如果我们修改 Target.apply 方法接受更多参数:
  class Target(object):
  def apply(value, are_you_sure):
  if are_you_sure:
  return value
  else:
  return None
  重新运行你的测试,你会发现它仍能通过。这是因为它不是针对你的 API 创建的。这是为什么你总是应该使用 create_autospec 方法,并且在使用 @patch 和 @patch.object 装饰方法时使用 autospec 参数。
  现实例子:模拟 Facebook API 调用
  作为这篇文章的结束,我们写一个更加适用的现实例子,一个在介绍中提及的功能:发布消息到 Facebook。我将写一个不错的包装类及其对应的测试用例。
  import facebook
  class SimpleFacebook(object):
  def __init__(self, oauth_token):
  self.graph = facebook.GraphAPI(oauth_token)
  def post_message(self, message):
  """Posts a message to the Facebook wall."""
  self.graph.put_object("me", "feed", message=message)
  这是我们的测试用例,它可以检查我们发布的消息,而不是真正地发布消息:
  import facebook
  import simple_facebook
  import mock
  import unittest
  class SimpleFacebookTestCase(unittest.TestCase):
  @mock.patch.object(facebook.GraphAPI, 'put_object', autospec=True)
  def test_post_message(self, mock_put_object):
  sf = simple_facebook.SimpleFacebook("fake oauth token")
  sf.post_message("Hello World!")
  # verify
  mock_put_object.assert_called_with(message="Hello World!")
  正如我们所看到的,在 Python 中,通过 mock,我们可以非常容易地动手写一个更加智能的测试用例。
  Python Mock 总结
  即使对它的使用还有点不太熟悉,对 单元测试 来说,Python 的 mock 库可以说是一个规则改变者。我们已经演示了常见的用例来了解了 mock 在单元测试中的使用,希望这篇文章能够帮助 Python 开发者 克服初期的障碍,写出、经受过考验的代码。