Question Comment un module python simule / stub comme urllib


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

Quelle est la meilleure façon de contrôler ce que retourne urllib.urlopen?


64
2017-11-17 12:07


origine


Réponses:


Une autre approche simple consiste à faire en sorte que vos tests remplacent les urllib urlopen() fonction. 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 invoquent des fonctions dans mymodule, dummy_urlopen() sera appelé au lieu du réel urlopen(). Les langages dynamiques comme Python facilitent l'extraction des méthodes et des classes pour les tests.

Voir mes billets de blog à http://softwarecorner.wordpress.com/ Pour plus d'informations sur la suppression des dépendances pour les tests.


89
2017-11-17 12:32



j'utilise Mock 's décorateur de patch:

from mock import patch

[...]

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

67
2018-03-06 15:31



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
2017-11-17 12:49



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
2017-11-08 20:01



La meilleure façon de gérer cela est probablement de diviser le code, de sorte 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 de l'extracteur dans la logique de traitement, puis vous pouvez facilement le remplacer par un récupérateur de simulation 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
2017-11-17 12:23



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

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
2017-07-11 00:12



La manière la plus simple est de changer votre fonction pour qu'elle n'utilise pas nécessairement urllib.urlopen. Disons que ceci est votre fonction originale:

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 simulée 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
2017-11-17 12:26