Führt der eigene Code http-Calls aus, z.B. um Dateien von einem externen Server runterzuladen stellt sich die Frage nach der Testbarkeit. Idealerweise ist der Code so aufgebaut, daß er nicht direkt von einer http-Library abhängt und man entsprechende Calls mocken kann. Ist dies nicht einfach möglich, kann man den in der Standarlibrary eingebauten http.server
als localhost-Gegenstelle für Tests verwenden.
Der Trick ist dabei http.server
in einem eigenen Prozeß auf einem freien Port zu starten, damit test-Code und mock-Server nicht blockieren. Multithreading bietet sich hier nicht an wegen dem Global Interpreter Lock (GIL).
Die Info über einen freien Port liefert übrigens socketserver.TCPServer
, wenn man diesen testweise mit einem Port von “0” instantiiert:
with socketserver.TCPServer(("localhost", 0), None) as server:
self.port = server.server_address[1]
Mit einer komfortablen Wrapper-Klasse außenrum lässt sich der Prozeß vom Test-Runner starten und stoppen.
Der komplette Code inkl. Beispielen befindet sich auf github, hier nur der relevante Ausschnitt:
class HttpServer():
'''Simple http server'''
def __init__(self, folder):
self.port = 0
self.process = None
self.folder = folder
def _runner(self):
'''Starts the http server'''
server_address = ('localhost', self.port)
server_class = http.server.HTTPServer
os.chdir(self.folder)
handler_class = http.server.SimpleHTTPRequestHandler
httpd = server_class(server_address, handler_class)
httpd.serve_forever()
def start(self):
'''Starts the http server in a background process'''
with socketserver.TCPServer(("localhost", 0), None) as server:
self.port = server.server_address[1]
self.process = multiprocessing.Process(target=self._runner, args=())
self.process.start()
# Sleep a bit to make sure port is actually served
time.sleep(0.1)
def stop(self):
'''Stops the http background server'''
self.process.terminate()
self.process = None
self.port = 0