Frage

Ich habe ein Stück Code, den ich nicht herausfinden kann, wie man einen Unit -Test hat! Das Modul zieht den Inhalt von externen XML -Feeds (Twitter, Flickr, YouTube usw.) mit Urllib2. Hier ist ein Pseudo-Code dafür:

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

Mein erster Gedanke war, die Antwort zu überlegen und sie zum Testen zu laden, aber anscheinend ist Urllibs Antwortobjekt unverzehrbar (es legt eine Ausnahme auf).

Das Speichern der XML aus dem Antwortkörper ist nicht ideal, da mein Code auch die Header -Informationen verwendet. Es wurde entwickelt, um auf ein Antwortobjekt zu reagieren.

Und natürlich ist es a entsetzlich Idee.

Wie schreibe ich dafür einen Unit -Test?

War es hilfreich?

Lösung

Urllib2 hat eine Funktion genannt build_opener() und install_opener() mit dem Sie das Verhalten von verspotten sollten 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

Andere Tipps

Es wäre am besten, wenn Sie einen Schein -Urlopen schreiben könnten (und möglicherweise Anfrage), das die minimal erforderliche Schnittstelle bietet, die sich wie die Version von Urllib2 verhalten kann. Sie müssen dann Ihre Funktion/Methode haben, mit der sie diesen Schein -Urlopen irgendwie akzeptieren und verwenden können urllib2.urlopen Andernfalls.

Dies ist eine Menge Arbeit, aber lohnt sich. Denken Sie daran, dass Python sehr freundlich zu Ducktyping ist.

Zum Beispiel:

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

Zugegeben, einige davon sind schwer zu verspotten, denn zum Beispiel glaube ich, dass die normalen "Header" ein httpMessage sind, das lustige Dinge wie unempfindliche Headernamen implementiert. Möglicherweise können Sie jedoch einfach eine HTTPMessage mit Ihren Antwortdaten konstruieren.

Erstellen Sie eine separate Klasse oder Modul, die für die Kommunikation mit Ihren externen Feeds verantwortlich ist.

Machen Sie diese Klasse in der Lage, a zu sein Double testen. Du benutzt Python, also bist du dort ziemlich golden. Wenn Sie C#verwenden, würde ich entweder in Schnittstellen- oder virtuelle Methoden vorschlagen.

Fügen Sie in Ihrem Unit -Test ein Testdoppelte der externen Futtermittelklasse ein. Testen Sie, dass Ihr Code die Klasse korrekt verwendet, sofern die Klasse die Arbeit der Kommunikation mit Ihren externen Ressourcen ordnungsgemäß erledigt. Lassen Sie Ihren Test doppelt gefälschte Daten anstelle von Live -Daten haben. Testen Sie verschiedene Kombinationen der Daten und natürlich können die möglichen Ausnahmen urllib2 werfen.

Aand ... das war's.

Sie können Unit -Tests, die sich auf externe Quellen verlassen nicht tun. Führen Sie einen gelegentlichen Integrationstest in Ihrem Kommunikationsmodul durch, fügen Sie diese Tests jedoch nicht als Teil Ihrer automatisierten Tests hinzu.

Bearbeiten:

Nur eine Notiz über den Unterschied zwischen meiner Antwort und der Antwort von @Crast. Beide sind im Wesentlichen korrekt, beinhalten jedoch unterschiedliche Ansätze. In Crasts Ansatz verwenden Sie ein Test -Double in der Bibliothek selbst. In meinem Ansatz haben Sie die Verwendung der Bibliothek in ein separates Modul abstrahieren und das Doppel dieses Moduls testen.

Welcher Ansatz, den Sie verwenden, ist völlig subjektiv; Dort gibt es keine "richtige" Antwort. Ich bevorzuge meinen Ansatz, weil ich es ermöglicht, modularer, flexiblerer Code zu erstellen, was ich schätze. Es ist jedoch mit Kosten des zusätzlichen Code zu schreiben, was in vielen agilen Situationen möglicherweise nicht geschätzt wird.

Sie können verwenden Pymox das Verhalten von allem und jedem im Urllib2 (oder eines anderen) Pakets verspotten. Es ist 2010, Sie sollten Ihre eigenen Scheinkurse nicht schreiben.

Ich denke, am einfachsten ist es, in Ihrem Unit -Test tatsächlich einen einfachen Webserver zu erstellen. Wenn Sie den Test starten, erstellen Sie einen neuen Thread, der auf einem beliebigen Port hört. Wenn ein Client eine Verbindung herstellt, gibt es nur einen bekannten Satz von Header und XML zurück, und beendet dann.

Ich kann näher erläutern, wenn Sie weitere Informationen benötigen.

Hier ist ein Code:

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

Um Ihren Unit -Test auszuführen, rufen Sie an serve_data() Rufen Sie dann Ihren Code an, der eine URL anfordert, die aussieht wie http://localhost:12345/anythingyouwant.

Warum nicht nur eine Website verspotten Das gibt die Antwort zurück, die Sie erwarten? Starten Sie dann den Server in einem Thread in Setup und töten Sie ihn im Abreißdown. Am Ende habe ich dies zum Testen von Code gemacht, die E -Mails senden, indem ich einen SMTP -Server verspottet, und es funktioniert großartig. Sicherlich könnte etwas Trivialeres für HTTP getan werden ...

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"

Ich versuche, mich bei @John-La-Rooy-Antwort ein wenig zu verbessern, und habe eine kleine Klasse erstellt, die ein einfaches Verspotten für Unit-Tests ermöglicht

Sollte mit Python 2 und 3 arbeiten

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)

So verwendet:

class TestOther(unittest.TestCase):

    def setUp(self):
        previous = MockHTTPHandler.install()
        self.addCleanup(MockHTTPHandler.remove, previous)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top