2008-11-17 7 views
64

Je dois tester une fonction qui a besoin d'interroger une page sur un serveur externe en utilisant urllib.urlopen (il utilise aussi urllib.urlencode). Le serveur pourrait être en panne, la page pourrait changer; Je ne peux pas compter dessus pour un test.Comment peut on simuler/stub module python comme urllib

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

Répondre

88

Une autre approche simple est d'avoir votre override test de 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 celui-ci:

import mymodule 

def dummy_urlopen(url): 
    ... 

mymodule.urllib.urlopen = dummy_urlopen 

Ensuite, lorsque vos tests en appel à des fonctions mymodule, dummy_urlopen() seront appelés au lieu du réel urlopen(). Les langages dynamiques tels que Python facilitent le remplacement des méthodes et des classes pour les tests.

Pour plus d'informations sur le blocage des dépendances pour les tests, consultez les billets de mon blog au http://softwarecorner.wordpress.com/.

+11

Monkeypatches pour tester sont une chose pratique. En effet, c'est probablement l'exemple canonique du «bon monkeypatch». –

+0

http://visionandexecution.org semble être en panne. Y at-il un autre lien, ou est-ce parti maintenant? –

+1

Je n'ai pas posté sur le blog depuis très longtemps, mais je l'ai fait porter sur http://softwarecorner.wordpress.com/ –

8

Probablement la meilleure façon de gérer cela est 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 simulateur 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 
3

La manière la plus simple est de changer votre fonction afin qu'elle n'utilise pas nécessairement urllib.urlopen. Disons que c'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 maquette à 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

Je ne suis pas sûr que j'aime que le projecteur testé connaisse les détails de configuration ... Cependant, cela fonctionne. –

+1

Je ne vois rien de mal à paramétrer la fonction. Il n'y a aucune connaissance ici de comment urlopen pourrait être truqué ou pourquoi, juste que cela pourrait se produire. –

27

Avez-vous donné Mox un coup d'oeil? Il devrait faire tout ce dont vous avez besoin. Voici un simple session interactive 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 ...> 
67

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() 
+3

dommage que cela ne fonctionne pas lors de la correction des fonctions du module:/(au moins pas 0.7.2) –

+2

pas 100% vrai, si vous importez la fonction avant de patcher ça marche, sinon le patch échoue silencieusement (pas d'erreurs, rien ne se corrige : /) –

+2

Bon point là; Le correctif doit renvoyer des erreurs lorsqu'il ne parvient pas à trouver le module approprié plutôt que de simplement échouer en mode silencieux. – fatuhoku

7

Si vous ne voulez pas charger même 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 puis:

>>> 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' 
+0

Fermez, mais la fonction d'importation n'importera pas vraiment de choses. Ainsi, un appelant utilisant urlibib * n'obtiendra pas les fonctions dont il a besoin –

13

HTTPretty fonctionne exactement de la même manière que FakeWeb. HTTPretty fonctionne dans la couche socket, donc il devrait fonctionner en interceptant toutes les bibliothèques client http python. Il est testé en combat contre urllib2, httplib2 et les demandes

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" 
+0

En 2013, c'est définitivement la meilleure réponse. Votons la superbe bibliothèque de Falcão, les gars! – fatuhoku

+0

Venant d'un angle Obj-C, je cherchais quelque chose comme [OHHTTPStubs] (https://github.com/AliSoftware/OHHTTPStubs) pour Python. Je suis ravi de trouver HTTPretty. – fatuhoku

Questions connexes