Мне нужно протестировать функцию, которая должна запрашивать страницу на внешнем сервере с помощью urllib.urlopen(она также использует urllib.urlencode). Сервер может быть недоступен, страница может измениться; Я не могу полагаться на это для теста.
Каков наилучший способ управления возвратом urllib.urlopen?
Ответ 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
, вместо реального urlopen()
будет вызываться dummy_urlopen()
. Динамические языки, такие как Python, делают его очень легким для исключения методов и классов для тестирования.
См. мои сообщения в блоге http://softwarecorner.wordpress.com/ для получения дополнительной информации о настройке зависимостей для тестов.
Ответ 2
Я использую Mock's:
from mock import patch
[...]
@patch('urllib.urlopen')
def test_foo(self, urlopen_mock):
urlopen_mock.return_value = MyUrlOpenMock()
Ответ 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 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 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"
Ответ 5
Вероятно, лучший способ справиться с этим - разделить код, чтобы логика, обрабатывающая содержимое страницы, была разделена на код, который извлекает страницу.
Затем передайте экземпляр кода получателя в логику обработки, затем вы можете легко заменить его приманкой для unit test.
например.
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
Ответ 6
Если вы не хотите даже загружать модуль:
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'
Ответ 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)
Ответ 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