Python: How to unit test a custom HTTP request Handler?

前端 未结 3 1359
自闭症患者
自闭症患者 2021-02-20 17:12

I have a custom HTTP request handler that can be simplified to something like this:

# Python 3:
from http import server

c         


        
相关标签:
3条回答
  • 2021-02-20 17:50

    Here's one approach I came up with to mock the server. Note that this should be compatible with both Python 2 and python 3. The only issue is that I can't find a way to access the result of the GET request, but at least the test will catch any exceptions it comes across!

    try:
        # Python 2.x
        import BaseHTTPServer as server
        from StringIO import StringIO as IO
    except ImportError:
        # Python 3.x
        from http import server
        from io import BytesIO as IO
    
    
    class MyHandler(server.BaseHTTPRequestHandler):
        """Custom handler to be tested"""
        def do_GET(self):
            # print just to confirm that this method is being called
            print("executing do_GET") # just to confirm...
    
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
    
            # Here's where all the complicated logic is done to generate HTML.
            # For clarity here, replace with a simple stand-in:
            html = "<html><p>hello world</p></html>"
    
            self.wfile.write(html.encode())
    
    
    def test_handler():
        """Test the custom HTTP request handler by mocking a server"""
        class MockRequest(object):
            def makefile(self, *args, **kwargs):
                return IO(b"GET /")
    
        class MockServer(object):
            def __init__(self, ip_port, Handler):
                handler = Handler(MockRequest(), ip_port, self)
    
        # The GET request will be sent here
        # and any exceptions will be propagated through.
        server = MockServer(('0.0.0.0', 8888), MyHandler)
    
    
    test_handler()
    
    0 讨论(0)
  • 2021-02-20 18:14

    Expanding on the answer from jakevdp, I managed to be able to check the output, too:

    try:
        import unittest2 as unittest
    except ImportError:
        import unittest
    try:
        from io import BytesIO as IO
    except ImportError:
        from StringIO import StringIO as IO
    from server import MyHandlerSSL  # My BaseHTTPRequestHandler child
    
    
    class TestableHandler(MyHandlerSSL):
        # On Python3, in socketserver.StreamRequestHandler, if this is
        # set it will use makefile() to produce the output stream. Otherwise,
        # it will use socketserver._SocketWriter, and we won't be able to get
        # to the data
        wbufsize = 1
    
        def finish(self):
            # Do not close self.wfile, so we can read its value
            self.wfile.flush()
            self.rfile.close()
    
        def date_time_string(self, timestamp=None):
            """ Mocked date time string """
            return 'DATETIME'
    
        def version_string(self):
            """ mock the server id """
            return 'BaseHTTP/x.x Python/x.x.x'
    
    
    class MockSocket(object):
        def getsockname(self):
            return ('sockname',)
    
    
    class MockRequest(object):
        _sock = MockSocket()
    
        def __init__(self, path):
            self._path = path
    
        def makefile(self, *args, **kwargs):
            if args[0] == 'rb':
                return IO(b"GET %s HTTP/1.0" % self._path)
            elif args[0] == 'wb':
                return IO(b'')
            else:
                raise ValueError("Unknown file type to make", args, kwargs)
    
    
    class HTTPRequestHandlerTestCase(unittest.TestCase):
        maxDiff = None
    
        def _test(self, request):
            handler = TestableHandler(request, (0, 0), None)
            return handler.wfile.getvalue()
    
        def test_unauthenticated(self):
            self.assertEqual(
                    self._test(MockRequest(b'/')),
                    b"""HTTP/1.0 401 Unauthorized\r
    Server: BaseHTTP/x.x Python/x.x.x\r
    Date: DATETIME\r
    WWW-Authenticate: Basic realm="MyRealm", charset="UTF-8"\r
    Content-type: text/html\r
    \r
    <html><head><title>Authentication Failed</title></html><body><h1>Authentication Failed</h1><p>Authentication Failed. Authorised Personnel Only.</p></body></html>"""
                    )
    
    
    def main():
        unittest.main()
    
    
    if __name__ == "__main__":
        main()
    

    The code I am testing returns a 401 Unauthorised for "/". Change the response as appopriate for your test case.

    0 讨论(0)
  • 2021-02-20 18:15

    So this is a little tricky depending on how "deep" you want to go into the BaseHTTPRequestHandler behavior to define your unit test. At the most basic level I think you can use this example from the mock library:

    >>> from mock import MagicMock
    >>> thing = ProductionClass()
    >>> thing.method = MagicMock(return_value=3)
    >>> thing.method(3, 4, 5, key='value')
    3
    >>> thing.method.assert_called_with(3, 4, 5, key='value')
    

    So if you know which methods in the BaseHTTPRequestHandler your class is going to call you could mock the results of those methods to be something acceptable. This can of course get pretty complex depending on how many different types of server responses you want to test.

    0 讨论(0)
提交回复
热议问题