返回介绍

6.3 模拟(mocking)

发布于 2024-01-23 21:41:46 字数 4224 浏览 0 评论 0 收藏 0

mock对象即模拟对象,用来通过某种特殊和可控的方式模拟真实应用程序对象的行为。在创建精确地描述测试代码的状态的环境时,它们非常有用。

如果正在开发一个HTTP客户端,要想部署HTTP服务器并测试所有场景,令其返回所有可能值,几乎是不可能的(至少会非常复杂)。此外,测试所有失败场景也是极其困难的。

一种更简单的方式是创建一组根据这些特定场景进行建模的mock对象,并利用它们作为测试环境对代码进行测试。

Python标准库中用来创建mock对象的库名为mock(https://pypi.python.org/pypi/mock/ )。从Python 3.3开始,它被命名为unit.mock,合并到Python标准库。因此可以使用下面的代码片段:

try:
  from unittest import mock
except ImportError:
  import mock

要保持Python 3.3和之前版本之间的向后兼容。

它使用起来也非常简单,如示例6.6所示。

示例6.6 mock的基本用法

>>> import mock
>>> m = mock.Mock()
>>> m.some_method.return_value = 42
>>> m.some_method()
42
>>> def print_hello():
...
print("hello world!")
...
>>> m.some_method.side_effect = print_hello
>>> m.some_method()
hello world!
>>> def print_hello():
... print("hello world!")
... return 43
...
>>> m.some_method.side_effect = print_hello
>>> m.some_method()
hello world!
43
>>> m.some_method.call_count
3

即使只使用这一组功能,也应该可以模拟许多内部对象以用于不同的数据场景中。

模拟使用动作/断言模式,也就是说一旦测试运行,必须确保模拟的动作被正确地执行,如示例6.7所示。

示例6.7 确认方法调用

>>> import mock
>>> m = mock.Mock()
>>> m.some_method('foo', 'bar')
<Mock name='mock.some_method()' id='26144272'>
>>> m.some_method.assert_called_once_with('foo', 'bar')
>>> m.some_method.assert_called_once_with('foo', mock.ANY)
>>> m.some_method.assert_called_once_with('foo', 'baz')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in
    assert_called_once_with
  return self.assert_called_with(*args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in
    assert_called_with
  raise AssertionError(msg)
AssertionError: Expected call: some_method('foo', 'baz')
Actual call: some_method('foo', 'bar')

显然,很容易传一个mock对象到代码的任何部分,并在其后检查代码是否按其期望的传入参数被调用。如果不知道该传入何种参数,可以使用mock.ANY作为参数值传入,它将会匹配传递给mock方法的任何参数。

有时可能需要来自外部模块的函数、方法或对象。mock库为此提供了一组补丁函数。

示例6.8 使用mock.patch

>>> import mock
>>> import os
>>> def fake_os_unlink(path):
...   raise IOError("Testing!")
...
>>> with mock.patch('os.unlink', fake_os_unlink):
...  os.unlink('foobar')
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in fake_os_unlink
IOError: Testing!

通过mock.pach方法,可以修改外部代码的任何部分,使其按照需要的方式对软件进行各种条件下的测试,如示例6.9所示。

示例6.9 使用mock.patch测试一组行为

import requests
import unittest
import mock

class WhereIsPythonError(Exception):
  pass

def is_python_still_a_programming_language():
  try:
    r = requests.get("http://python.org")
  except IOError:
    pass
  else:
    if r.status_code == 200:
      return 'Python is a programming language' in r.content
  raise WhereIsPythonError("Something bad happened")

def get_fake_get(status_code, content):
  m = mock.Mock()
  m.status_code = status_code
  m.content = content
  def fake_get(url):
    return m
  return fake_get

def raise_get(url):
  raise IOError("Unable to fetch url %s" % url)
class TestPython(unittest.TestCase):
  @mock.patch('requests.get', get_fake_get(
    200, 'Python is a programming language for sure'))
  def test_python_is(self):
    self.assertTrue(is_python_still_a_programming_language())

  @mock.patch('requests.get', get_fake_get(
    200, 'Python is no more a programming language'))
  def test_python_is_not(self):
    self.assertFalse(is_python_still_a_programming_language())

  @mock.patch('requests.get', get_fake_get(
    404, 'Whatever'))
  def test_bad_status_code(self):
    self.assertRaises(WhereIsPythonError,
              is_python_still_a_programming_language)

  @mock.patch('requests.get', raise_get)
  def test_ioerror(self):
    self.assertRaises(WhereIsPythonError,
              is_python_still_a_programming_language)

示例6.9使用了mock.patch的装饰器版本,这并不改变它的行为,但当需要在整个测试函数的上下文内使用模拟时这会更方便。

使用模拟可以很方便地模拟任何问题,如Web服务器返回404错误,或者发生网络问题。我们可以确定代码返回的是正确的值,或在每种情况下抛出正确的异常,总之确保代码总是按照预期行事。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文