Pregunta

Tengo un trozo de código que no puedo encontrar la manera de probar la unidad! El módulo tira de contenidos a partir de cargas externas XML (Twitter, Flickr, YouTube, etc.) con urllib2. He aquí algunos pseudo-código para ello:

params = (url, urlencode(data),) if data else (url,)
req = Request(*params)
response = urlopen(req)
#check headers, content-length, etc...
#parse the response XML with lxml...

Mi primer pensamiento fue de estibar la respuesta y la carga de la prueba, pero al parecer objeto de respuesta de urllib es unserializable (se genera una excepción).

Sólo guardar el XML desde el cuerpo de la respuesta no es ideal, porque mi código utiliza la información de la cabecera también. Está diseñado para actuar sobre un objeto respuesta.

Y, por supuesto, contando con una fuente externa de datos en una unidad de prueba es un horribles idea.

Entonces, ¿cómo puedo escribir una prueba unitaria para esto?

¿Fue útil?

Solución

urllib2 tiene unas funciones llamadas build_opener() y install_opener() que se debe utilizar para burlarse del comportamiento de urlopen()

import urllib2
from StringIO import StringIO

def mock_response(req):
    if req.get_full_url() == "http://example.com":
        resp = urllib2.addinfourl(StringIO("mock file"), "mock message", req.get_full_url())
        resp.code = 200
        resp.msg = "OK"
        return resp

class MyHTTPHandler(urllib2.HTTPHandler):
    def http_open(self, req):
        print "mock opener"
        return mock_response(req)

my_opener = urllib2.build_opener(MyHTTPHandler)
urllib2.install_opener(my_opener)

response=urllib2.urlopen("http://example.com")
print response.read()
print response.code
print response.msg

Otros consejos

Sería mejor si usted podría escribir un simulacro urlopen (y posiblemente Solicitud), que proporciona la interfaz mínima necesaria para comportarse como la versión de urllib2. A continuación, tendría que tener su función / método que utiliza es capaz de aceptar este urlopen simulacro de alguna manera, y utilizar urllib2.urlopen lo contrario.

Esta es una buena cantidad de trabajo, pero vale la pena. Recuerde que el pitón es muy amigable para Duck Typing, por lo que sólo necesita proporcionar cierta apariencia de las propiedades del objeto respuesta a burlarse de él.

Por ejemplo:

class MockResponse(object):
    def __init__(self, resp_data, code=200, msg='OK'):
        self.resp_data = resp_data
        self.code = code
        self.msg = msg
        self.headers = {'content-type': 'text/xml; charset=utf-8'}

    def read(self):
        return self.resp_data

    def getcode(self):
        return self.code

    # Define other members and properties you want

def mock_urlopen(request):
    return MockResponse(r'<xml document>')

Por supuesto, algunos de estos son difíciles de burlarse, porque por ejemplo yo creo que las "cabeceras" normales es un HttpMessage que implementa cosas divertidas, como nombres de encabezado de mayúsculas y minúsculas. Sin embargo, es posible que pueda simplemente construir un HttpMessage con sus datos de respuesta.

Construir una clase separada o módulo responsable de comunicarse con sus alimentaciones externas.

Hacer esta clase capaz de ser un prueba doble . Usted está utilizando Python, por lo que está bastante de oro allí; si estuviera usando C #, me gustaría sugerir ya sea en la interfaz o virtuales métodos.

En la prueba de unidad, inserte una prueba doble de la clase de alimentación externa. Prueba de que el código utiliza la clase correcta, en el supuesto de que la clase hace el trabajo de comunicarse correctamente con sus recursos externos. Tener su prueba de doble retorno de datos falsos en lugar de datos en tiempo real; probar varias combinaciones de los datos y, por supuesto, las posibles excepciones urllib2 podría lanzar.

A y ... eso es todo.

No se puede automatizar de manera efectiva las pruebas unitarias que dependen de fuentes externas, por lo que es el mejor de no hacerlo . Ejecutar una prueba de integración de vez en cuando en su módulo de comunicación, pero no incluyen las pruebas como parte de sus pruebas automatizadas.

Editar

Sólo una nota en la diferencia entre mi respuesta y la respuesta de @ Crast. Ambos son esencialmente correcta, pero implican diferentes enfoques. En el enfoque de Crast, se utiliza una doble prueba en la propia biblioteca. En mi enfoque, se resumen el uso de la biblioteca de distancia en un módulo separado y doble prueba de ese módulo.

¿Qué enfoque que se utiliza es totalmente subjetivo; no hay una respuesta "correcta" allí. Prefiero que mi enfoque, ya que me permite construir más modular, flexible, código, algo que valoro. Pero tiene un costo en términos de código adicional para escribir, algo que no puede ser valorado en muchas situaciones ágiles.

Puede utilizar pymox burlarse del comportamiento de cualquier cosa y todo en el urllib2 (o cualquier otra) paquete. Es 2010, no se debe escribir sus propias clases simuladas.

Creo que la cosa más fácil de hacer es crear realmente un servidor web sencillo en su unidad de prueba. Al iniciar la prueba, crear un nuevo hilo que escucha en un puerto arbitrario y cuando un cliente se conecta sólo devuelve un conjunto conocido de encabezados y XML, y luego termina.

Puedo elaborar si necesita más información.

Aquí hay algo de código:

import threading, SocketServer, time

# a request handler
class SimpleRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(102400) # token receive
        senddata = file(self.server.datafile).read() # read data from unit test file
        self.request.send(senddata)
        time.sleep(0.1) # make sure it finishes receiving request before closing
        self.request.close()

def serve_data(datafile):
    server = SocketServer.TCPServer(('127.0.0.1', 12345), SimpleRequestHandler)
    server.datafile = datafile
    http_server_thread = threading.Thread(target=server.handle_request())

Para ejecutar la prueba de la unidad, llame serve_data() luego llame a su código que solicita una dirección URL que se parece a http://localhost:12345/anythingyouwant.

¿Por qué no burlarse de un sitio web que devuelve la respuesta se puede esperar? a continuación, iniciar el servidor en un hilo en la configuración y matarlo en el desmontaje. Terminé haciendo esto para probar código que se envía un correo electrónico al burlarse de un servidor SMTP y funciona muy bien. Sin duda algo más trivial se podía hacer por http ...

from smtpd import SMTPServer
from time import sleep
import asyncore
SMTP_PORT = 6544

class MockSMTPServer(SMTPServer):
    def __init__(self, localaddr, remoteaddr, cb = None):
        self.cb = cb
        SMTPServer.__init__(self, localaddr, remoteaddr)

    def process_message(self, peer, mailfrom, rcpttos, data):
        print (peer, mailfrom, rcpttos, data)
        if self.cb:
            self.cb(peer, mailfrom, rcpttos, data)
        self.close()

def start_smtp(cb, port=SMTP_PORT):

    def smtp_thread():
        _smtp = MockSMTPServer(("127.0.0.1", port), (None, 0), cb)
        asyncore.loop()
        return Thread(None, smtp_thread)


def test_stuff():
        #.......snip noise
        email_result = None

        def email_back(*args):
            email_result = args

        t = start_smtp(email_back)
        t.start()
        sleep(1)

        res.form["email"]= self.admin_email
        res = res.form.submit()
        assert res.status_int == 302,"should've redirected"


        sleep(1)
        assert email_result is not None, "didn't get an email"

Tratar de mejorar un poco en respuesta @ John-la-Rooy, he hecho una pequeña clase de burla simple para permitir pruebas de unidad

En caso de trabajar con el pitón 2 y 3

try:
    import urllib.request as urllib
except ImportError:
    import urllib2 as urllib

from io import BytesIO


class MockHTTPHandler(urllib.HTTPHandler):

    def mock_response(self, req):
        url = req.get_full_url()

        print("incomming request:", url)

        if url.endswith('.json'):
            resdata = b'[{"hello": "world"}]'
            headers = {'Content-Type': 'application/json'}

            resp = urllib.addinfourl(BytesIO(resdata), header, url, 200)
            resp.msg = "OK"

            return resp
        raise RuntimeError('Unhandled URL', url)
    http_open = mock_response


    @classmethod
    def install(cls):
        previous = urllib._opener
        urllib.install_opener(urllib.build_opener(cls))
        return previous

    @classmethod
    def remove(cls, previous=None):
        urllib.install_opener(previous)

Se utiliza como esto:

class TestOther(unittest.TestCase):

    def setUp(self):
        previous = MockHTTPHandler.install()
        self.addCleanup(MockHTTPHandler.remove, previous)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top