Wie testet ich Unit ein Modul, das auf Urllib2 beruht?
-
21-09-2019 - |
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?
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)