web-dev-qa-db-fra.com

Comment un module python simulé/stub comme urllib

Je dois tester une fonction qui doit interroger une page sur un serveur externe en utilisant urllib.urlopen (il utilise également urllib.urlencode). Le serveur pourrait être en panne, la page pourrait changer; Je ne peux pas compter dessus pour un test.

Quel est le meilleur moyen de contrôler les retours d'urllib.urlopen?

64
Dinoboff

Une autre approche simple consiste à remplacer votre test par la fonction urlopen() de urllib. Par exemple, si votre module a

import urllib

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

Vous pouvez définir votre test comme ceci:

import mymodule

def dummy_urlopen(url):
    ...

mymodule.urllib.urlopen = dummy_urlopen

Ensuite, lorsque vos tests invoqueront des fonctions dans mymodule, dummy_urlopen() sera appelé à la place de la fonction urlopen() réelle. Les langages dynamiques tels que Python facilitent l'extraction de méthodes et de classes à des fins de test.

Voir les articles de mon blog à l'adresse http://softwarecorner.wordpress.com/ pour plus d'informations sur l'élimination des dépendances lors des tests.

92
Clint Miller

J'utilise Mock's patch decorator:

from mock import patch

[...]

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

Avez-vous donné Mox un regard? Il devrait faire tout ce dont vous avez besoin. Voici une session interactive simple illustrant la solution dont vous avez besoin:

>>> 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 ...>
27
Damir Zekić

HTTPretty fonctionne exactement de la même manière que FakeWeb. HTTPretty fonctionne dans la couche socket, il devrait donc intercepter toutes les bibliothèques clientes python http. Il est testé contre urllib2, httplib2 et les requêtes

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"
14
Gabriel Falcão

Le meilleur moyen de gérer cela est probablement de scinder le code afin que la logique qui traite le contenu de la page soit séparée du code qui récupère la page.

Ensuite, passez une instance du code d'extraction dans la logique de traitement, puis remplacez-la facilement par un simulateur d'extraction pour le test unitaire.

par exemple.

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
8
Douglas Leeder

Si vous ne voulez même pas charger le module:

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.__= 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

Et alors:

>>> 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
ilpoldo

Le moyen le plus simple est de changer votre fonction afin qu'elle n'utilise pas nécessairement urllib.urlopen. Disons que ceci est votre fonction d'origine:

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

Ajoutez un argument qui est la fonction à utiliser pour ouvrir l'URL. Ensuite, vous pouvez fournir une fonction fictive pour faire tout ce dont vous avez besoin:

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)
3
Ned Batchelder

Ajoutant à la réponse de Clint Miller, pour ce faire, j'ai dû créer une fausse classe qui implémente une méthode de lecture comme celle-ci:

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

Puis stub out urllib2.open:

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