【问题标题】:Python: How to unit test a custom HTTP request Handler?Python:如何对自定义 HTTP 请求处理程序进行单元测试?
【发布时间】:2014-10-11 16:57:35
【问题描述】:

我有一个自定义的 HTTP 请求处理程序,可以简化为:

# Python 3:
from http import server

class MyHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        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())

我想对这个处理程序进行单元测试(即确保我的do_GET 无异常执行)而不实际启动 Web 服务器。是否有任何轻量级的方法来模拟 SimpleHTTPServer 以便我可以测试这段代码?

【问题讨论】:

  • import mock 和一点阅读应该可以解决问题

标签: python unit-testing simplehttpserver


【解决方案1】:

所以这有点棘手,具体取决于您想要进入BaseHTTPRequestHandler 行为的“深度”程度来定义您的单元测试。在最基本的层面上,我认为您可以使用mock 库中的this example

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

因此,如果您知道您的课程将调用BaseHTTPRequestHandler 中的哪些方法,您可以模拟这些方法的结果以使其成为可接受的结果。这当然会变得相当复杂,具体取决于您要测试多少不同类型的服务器响应。

【讨论】:

  • 谢谢 - 我花了一点时间玩这个,但我仍然卡住了。实例化MyHandler 需要request 对象,而获得request 对象似乎需要启动服务器。在没有实际启动服务器并生成请求的情况下,我仍然无法弄清楚如何创建或获取MyHandler 的实例。我将不得不花更多时间挖掘BaseHTTPServer 源,试图弄清楚如何模拟它......
  • 也许您可以尝试制作一个完全模拟的 request 对象,然后执行 old_check_request = request 然后 my_module.request = mock_request 之类的操作,然后创建符合您需要的行为。
  • 谢谢!查看我刚刚发布的可能解决方案。
【解决方案2】:

这是我想出的一种模拟服务器的方法。请注意,这应该与 Python 2 和 python 3 兼容。唯一的问题是我找不到访问GET 请求结果的方法,但至少测试会捕获它遇到的任何异常!

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

【讨论】:

  • 您最终找到了测试do_GET 方法内部功能的方法吗?
【解决方案3】:

扩展 jakevdp 的答案,我也设法检查了输出:

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

我正在测试的代码为“/”返回 401 Unauthorized。根据您的测试用例更改响应。

【讨论】:

    猜你喜欢
    • 2018-03-13
    • 1970-01-01
    • 2013-10-24
    • 1970-01-01
    • 2011-12-04
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多