2008-11-17 6 views
64

? Tengo que probar una función que necesita consultar una página en un servidor externo usando urllib.urlopen (también usa urllib.urlencode). El servidor podría estar caído, la página podría cambiar; No puedo confiar en ello para una prueba.¿Cómo se puede simular/resguardar un módulo de python como urllib

¿Cuál es la mejor manera de controlar lo que devuelve urllib.urlopen?

Respuesta

88

Otro enfoque simple es tener la función de anulación de la prueba urllib urlopen(). Por ejemplo, si su módulo tiene

import urllib 

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

Se podría definir su prueba como esta:

import mymodule 

def dummy_urlopen(url): 
    ... 

mymodule.urllib.urlopen = dummy_urlopen 

Entonces, cuando sus pruebas invocan funciones en mymodule, dummy_urlopen() serán llamados en lugar de la verdadera urlopen(). Los lenguajes dinámicos como Python hacen que sea muy fácil eliminar los métodos y las clases para probar.

Consulte las publicaciones de mi blog en http://softwarecorner.wordpress.com/ para obtener más información sobre cómo anular las dependencias para las pruebas.

+11

Los parches de prueba son muy útiles. De hecho, este es probablemente el ejemplo canónico "good monkeypatch". –

+0

http://visionandexecution.org parece estar fuera de servicio. ¿Hay otro enlace, o se ha ido esto ahora? –

+1

No publiqué en el blog en mucho tiempo, pero sí lo porté a http://softwarecorner.wordpress.com/ –

8

Probablemente la mejor manera de manejar esto es dividir el código, de modo que la lógica que procesa los contenidos de la página se divida desde el código que obtiene la página.

Luego pase una instancia del código de captación en la lógica de procesamiento, luego puede reemplazarlo fácilmente con una captación de simulacro para la prueba de unidad.

p. Ej.

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 manera más simple es cambiar su función para que no use necesariamente urllib.urlopen. Digamos que esta es su función original:

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

Agregue un argumento que es la función que se debe usar para abrir la URL. A continuación, puede proporcionar una función de maqueta para hacer lo que necesita:

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

No estoy seguro de que me gusta tener el dispositivo bajo prueba al tanto de los detalles de configuración ... Sin embargo, esto hace el trabajo. –

+1

No veo nada malo con la parametrización de la función. No se sabe aquí cómo se puede falsificar a urlopen o por qué, solo que podría suceder. –

27

¿Has regalado Mox un look? Debería hacer todo lo que necesita. Aquí es un simple sesión interactiva que ilustra la solución que necesita:

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

estoy usando Mock's parche decorador:

from mock import patch 

[...] 

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

es una lástima que no funciona cuando el parche funciones del módulo:/(al menos no 0.7.2) –

+2

no es 100% cierto, si importa la función antes de parchear funciona, de lo contrario el parche falla silenciosamente (no hay errores, simplemente nada se parchea : /) –

+2

Buen punto allí; la aplicación de parches debe arrojar errores cuando no puede encontrar el módulo relevante en lugar de simplemente fallar silenciosamente. – fatuhoku

7

En caso de que no desea incluso cargar el módulo:

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 

Y luego:

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

Cerrar, pero la función de importación no importará nada. Por lo tanto, una persona que llama desde urllib import * ... no obtendrá las funciones que necesita –

13

HTTPretty funciona de la misma manera que FakeWeb. HTTPretty funciona en la capa de socket, por lo que debería funcionar interceptando cualquier biblioteca de cliente http de python. Es probados en combate contra urllib2, httplib2 y pide

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, esta es definitivamente la mejor respuesta. ¡Votemos por la increíble biblioteca de Falcão, chicos! – fatuhoku

+0

Viniendo desde un ángulo Obj-C, estaba buscando algo como [OHHTTPStubs] (https://github.com/AliSoftware/OHHTTPStubs) para Python. Estoy encantado de encontrar HTTPretty. – fatuhoku

Cuestiones relacionadas