【问题标题】:How can one mock/stub python module like urllib一个模拟/存根 python 模块如何像 urllib
【发布时间】:2010-09-22 15:59:02
【问题描述】:

我需要测试一个需要使用 urllib.urlopen 查询外部服务器上的页面的函数(它也使用 urllib.urlencode)。服务器可能已关闭,页面可能会更改;我不能依赖它进行测试。

控制 urllib.urlopen 返回什么的最佳方法是什么?

【问题讨论】:

标签: python unit-testing testing mocking


【解决方案1】:

另一种简单的方法是让您的测试覆盖 urllib 的 urlopen() 函数。例如,如果您的模块有

import urllib

def some_function_that_uses_urllib():
    ...
    urllib.urlopen()
    ...

你可以这样定义你的测试:

import mymodule

def dummy_urlopen(url):
    ...

mymodule.urllib.urlopen = dummy_urlopen

然后,当您的测试调用mymodule 中的函数时,将调用dummy_urlopen() 而不是真正的urlopen()。像 Python 这样的动态语言使得为测试提取方法和类变得非常容易。

请参阅我在 http://softwarecorner.wordpress.com/ 上的博客文章,了解有关为测试排除依赖项的更多信息。

【讨论】:

  • 用于测试的猴子补丁是一件很方便的事情。事实上,这可能是典型的“好猴子补丁”示例。
  • visionandexecution.org 似乎已关闭。是否还有其他链接,或者现在已经没有了?
  • 好久没发博客了,不过我还是移植到softwarecorner.wordpress.com
  • 当心!如果您没有将模拟对象显式重置为原始值,这将对测试模块中的所有 urlopen 实例和模块中的其他类进行模拟。当然,在这种情况下,我不确定为什么有人想在单元测试中进行网络调用。我建议使用'with patch ...' 或@patch() 之类的东西,它可以让你更明确地控制你正在模拟的内容和限制。
  • 哇,这是模拟任何事物的最佳方式。在 JavaScript 中做同样的事情,很高兴在 Python 中看到同样的情况。谢谢。
【解决方案2】:

我正在使用Mock's 补丁装饰器:

from mock import patch

[...]

@patch('urllib.urlopen')
def test_foo(self, urlopen_mock):
    urlopen_mock.return_value = MyUrlOpenMock()

【讨论】:

  • 太糟糕了,它在修补模块功能时不起作用:/(至少不是 0.7.2)
  • 不是 100% 正确,如果你在修补它之前导入函数,否则修补会默默失败(没有错误,只是没有修补:/)
  • 好点;修补程序在找不到相关模块时应该抛出错误,而不是静默失败。
  • 它给了我一个错误。找不到夹具“urlopen_mock”
  • 如果你直接修补 urllib.urlopen,任何已经被模块导入的对它的引用都将保持未修补。为避免这种情况,请改为修补导入的参考。例如:补丁('mymodule.urlopen')
【解决方案3】:

你给Mox看了吗?它应该做你需要的一切。这是一个简单的交互式会话,说明了您需要的解决方案:

>>> import urllib
>>> # check that it works
>>> urllib.urlopen('http://www.google.com/')
<addinfourl at 3082723820L ...>
>>> # check what happens when it doesn't
>>> urllib.urlopen('http://hopefully.doesnotexist.com/')
#-- snip --
IOError: [Errno socket error] (-2, 'Name or service not known')

>>> # OK, let's mock it up
>>> import mox
>>> m = mox.Mox()
>>> m.StubOutWithMock(urllib, 'urlopen')
>>> # We can be verbose if we want to :)
>>> urllib.urlopen(mox.IgnoreArg()).AndRaise(
...   IOError('socket error', (-2, 'Name or service not known')))

>>> # Let's check if it works
>>> m.ReplayAll()
>>> urllib.urlopen('http://www.google.com/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.5/site-packages/mox.py", line 568, in __call__
    raise expected_method._exception
IOError: [Errno socket error] (-2, 'Name or service not known')

>>> # yay! now unset everything
>>> m.UnsetStubs()
>>> m.VerifyAll()
>>> # and check that it still works
>>> urllib.urlopen('http://www.google.com/')
<addinfourl at 3076773548L ...>

【讨论】:

【解决方案4】:

HTTPretty 的工作方式与 FakeWeb 完全相同。 HTTPretty 在套接字层工作,因此它应该可以拦截任何 python http 客户端库。它已经针对 urllib2、httplib2 和请求进行了实战测试

import urllib2
from httpretty import HTTPretty, httprettified


@httprettified
def test_one():
    HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/",
                           body="Find the best daily deals")

    fd = urllib2.urlopen('http://yipit.com')
    got = fd.read()
    fd.close()

    assert got == "Find the best daily deals"

【讨论】:

  • 在 2013 年,这绝对是最好的答案。让我们为 Falcão 的精彩图书馆投票吧,伙计们!
  • 从 Obj-C 的角度来看,我一直在为 Python 寻找类似 @​​987654322@ 的东西。我很高兴找到 HTTPretty。
【解决方案5】:

如果您甚至不想加载模块:

import sys,types
class MockCallable():
  """ Mocks a function, can be enquired on how many calls it received """
  def __init__(self, result):
    self.result  = result
    self._calls  = []

  def __call__(self, *arguments):
    """Mock callable"""
    self._calls.append(arguments)
    return self.result

  def called(self):
    """docstring for called"""
    return self._calls

class StubModule(types.ModuleType, object):
  """ Uses a stub instead of loading libraries """

  def __init__(self, moduleName):
    self.__name__ = moduleName
    sys.modules[moduleName] = self

  def __repr__(self):
    name  = self.__name__
    mocks = ', '.join(set(dir(self)) - set(['__name__']))
    return "<StubModule: %(name)s; mocks: %(mocks)s>" % locals()

class StubObject(object):
  pass

然后:

>>> urllib = StubModule("urllib")
>>> import urllib # won't actually load urllib

>>> urls.urlopen = MockCallable(StubObject())

>>> example = urllib.urlopen('http://example.com')
>>> example.read = MockCallable('foo')

>>> print(example.read())
'foo'

【讨论】:

  • 关闭,但导入功能不会真正导入东西。所以使用 from urllib import * ... 的调用者不会得到他们需要的函数
【解决方案6】:

处理此问题的最佳方法可能是拆分代码,以便将处理页面内容的逻辑与获取页面的代码分开。

然后将 fetcher 代码的实例传递给处理逻辑,然后您可以轻松地将其替换为模拟 fetcher 以进行单元测试。

例如

class Processor(oject):
    def __init__(self, fetcher):
        self.m_fetcher = fetcher

    def doProcessing(self):
        ## use self.m_fetcher to get page contents

class RealFetcher(object):
    def fetchPage(self, url):
        ## get real contents

class FakeFetcher(object):
    def fetchPage(self, url):
        ## Return whatever fake contents are required for this test

【讨论】:

    【解决方案7】:

    最简单的方法是更改​​您的函数,使其不必使用 urllib.urlopen。假设这是您的原始功能:

    def my_grabber(arg1, arg2, arg3):
        # .. do some stuff ..
        url = make_url_somehow()
        data = urllib.urlopen(url)
        # .. do something with data ..
        return answer
    

    添加一个参数,该参数是用于打开 URL 的函数。然后你可以提供一个模拟函数来做你需要的任何事情:

    def my_grabber(arg1, arg2, arg3, urlopen=urllib.urlopen):
        # .. do some stuff ..
        url = make_url_somehow()
        data = urlopen(url)
        # .. do something with data ..
        return answer
    
    def test_my_grabber():
        my_grabber(arg1, arg2, arg3, urlopen=my_mock_open)
    

    【讨论】:

    • 不确定我是否喜欢让被测夹具了解配置细节......但是,这确实有效。
    • 我看不出参数化函数有什么问题。这里不知道 urlopen 是如何被伪造的或为什么会被伪造,只是它可能会发生。
    【解决方案8】:

    加上克林特米勒的回答,为此我必须创建一个实现读取方法的假类,如下所示:

    class FakeURL:
        def read(foo):
            return '{"some":"json_text"}'
    

    然后存根 urllib2.open:

    # Stub out urllib2.open.
    def dummy_urlopen(foo, bar, baz):
      return FakeURL()
    urllib2.urlopen = dummy_urlopen
    

    【讨论】:

      猜你喜欢
      • 2014-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多