Как можно смоделировать / заглушить модуль Python, как urllib - PullRequest
65 голосов
/ 17 ноября 2008

Мне нужно протестировать функцию, которая должна запрашивать страницу на внешнем сервере, используя urllib.urlopen (он также использует urllib.urlencode). Сервер может быть недоступен, страница может измениться; Я не могу положиться на это для теста.

Как лучше всего контролировать, что возвращает urllib.urlopen?

Ответы [ 8 ]

94 голосов
/ 17 ноября 2008

Другой простой подход - использовать функцию urlopen() для переопределения вашего теста. Например, если ваш модуль имеет

import urllib

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

Вы можете определить свой тест следующим образом:

import mymodule

def dummy_urlopen(url):
    ...

mymodule.urllib.urlopen = dummy_urlopen

Тогда, когда ваши тесты вызовут функции в mymodule, вместо действительного urlopen() будет вызываться dummy_urlopen(). Динамические языки, такие как Python, упрощают тестирование методов и классов.

См. Мои сообщения в блоге по адресу http://softwarecorner.wordpress.com/ для получения дополнительной информации о блокировании зависимостей для тестов.

68 голосов
/ 06 марта 2010

Я использую Mock's патч декоратор:

from mock import patch

[...]

@patch('urllib.urlopen')
def test_foo(self, urlopen_mock):
    urlopen_mock.return_value = MyUrlOpenMock()
27 голосов
/ 17 ноября 2008

Вы смотрели на Мокс ? Он должен делать все, что вам нужно. Вот простой интерактивный сеанс, иллюстрирующий необходимое решение:

>>> 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 ...>
14 голосов
/ 09 ноября 2012

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"
8 голосов
/ 11 июля 2011

Если вы даже не хотите загружать модуль:

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'
8 голосов
/ 17 ноября 2008

Вероятно, лучший способ справиться с этим - разделить код, чтобы логика, обрабатывающая содержимое страницы, была отделена от кода, извлекающего страницу.

Затем передайте экземпляр кода сборщика в логику обработки, после чего вы можете легко заменить его на сборщик данных для модульного теста.

, например

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
3 голосов
/ 17 ноября 2008

Самый простой способ - изменить свою функцию, чтобы она не обязательно использовала 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)
0 голосов
/ 25 января 2019

Добавляя к ответу Клинта Миллера, для этого мне пришлось создать поддельный класс, который реализует метод чтения, подобный этому:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...