gpt4 book ai didi

python - 如何在Python的单元测试方案中模拟HTTP请求

转载 作者:IT老高 更新时间:2023-10-28 21:53:19 29 4
gpt4 key购买 nike

我想为与HTTP相关的所有测试都包括一个Web服务器。它不需要非常复杂。我希望不要依赖在线。因此,我可以测试程序的某些选项。

  • 启动服务器
  • 使用适当的mime类型,响应代码等创建一些资源(URI)。
  • 运行测试(最好也不必为每个测试启动服务器)
  • 关闭服务器。

  • 此代码上的任何提示都将有所帮助。我用BaseHTTPServer尝试了几件事,但还没有成功。 Nose 测试命令似乎无限期地等待。
    import unittest
    from foo import core

    class HttpRequests(unittest.TestCase):
    """Tests for HTTP"""

    def setUp(self):
    "Starting a Web server"
    self.port = 8080
    # Here we need to start the server
    #
    # Then define a couple of URIs and their HTTP headers
    # so we can test the code.
    pass

    def testRequestStyle(self):
    "Check if we receive a text/css content-type"
    myreq = core.httpCheck()
    myuri = 'http://127.0.0.1/style/foo'
    myua = "Foobar/1.1"
    self.asserEqual(myreq.mimetype(myuri, myua), "text/css")

    def testRequestLocation(self):
    "another test"
    pass

    def tearDown(self):
    "Shutting down the Web server"
    # here we need to shut down the server
    pass

    谢谢你的帮助。

    更新-2012:07:10T02:34:00Z

    对于给定的网站,这是一个代码,它将返回CSS列表。我想测试它是否返回正确的CSS列表。
    import unittest
    from foo import core

    class CssTests(unittest.TestCase):
    """Tests for CSS requests"""

    def setUp(self):
    self.css = core.Css()
    self.req = core.HttpRequests()

    def testCssList(self):
    "For a given Web site, check if we get the right list of linked stylesheets"
    WebSiteUri = 'http://www.opera.com/'
    cssUriList = [
    'http://www.opera.com/css/handheld.css',
    'http://www.opera.com/css/screen.css',
    'http://www.opera.com/css/print.css',
    'http://www.opera.com/css/pages/home.css']
    content = self.req.getContent(WebSiteUri)
    cssUriListReq = self.css.getCssUriList(content, WebSiteUri)
    # we need to compare ordered list.
    cssUriListReq.sort()
    cssUriList.sort()
    self.assertListEqual(cssUriListReq, cssUriList)

    然后在 foo/core.py
    import urlparse
    import requests
    from lxml import etree
    import cssutils

    class Css:
    """Grabing All CSS for one given URI"""


    def getCssUriList(self, htmltext, uri):
    """Given an htmltext, get the list of linked CSS"""
    tree = etree.HTML(htmltext)
    sheets = tree.xpath('//link[@rel="stylesheet"]/@href')
    for i, sheet in enumerate(sheets):
    cssurl = urlparse.urljoin(uri, sheet)
    sheets[i] = cssurl
    return sheets

    现在,该代码取决于在线服务器。它不应该。我希望能够添加大量不同类型的样式表组合,并测试协议(protocol),然后在其解析,组合等方面进行一些选择。

    最佳答案

    启动Web服务器进行单元测试绝对不是一个好习惯。单元测试应该简单且隔离,这意味着它们应避免执行IO操作。

    如果您要编写的实际上是单元测试,则应编写自己的测试输入,并查看mock objects。 Python是一种动态语言,模拟和猴子路径是编写单元测试的简单而强大的工具。特别要看一下出色的Mock module

    简单的单元测试

    因此,如果我们看看您的CssTests示例,您正在尝试测试css.getCssUriList是否能够提取您提供的HTML片段中引用的所有CSS样式表。您在此特定单元测试中所做的不是测试您可以发送请求并从网站获得响应,对吗?您只需要确保在给定一些HTML的情况下,您的函数即可返回正确的CSS URL列表。因此,在此测试中,您显然无需与真实的HTTP服务器对话。

    我将执行以下操作:

    import unittest

    class CssListTestCase(unittest.TestCase):

    def setUp(self):
    self.css = core.Css()

    def test_css_list_should_return_css_url_list_from_html(self):
    # Setup your test
    sample_html = """
    <html>
    <head>
    <title>Some web page</title>
    <link rel='stylesheet' type='text/css' media='screen'
    href='http://example.com/styles/full_url_style.css' />
    <link rel='stylesheet' type='text/css' media='screen'
    href='/styles/relative_url_style.css' />
    </head>
    <body><div>This is a div</div></body>
    </html>
    """
    base_url = "http://example.com/"

    # Exercise your System Under Test (SUT)
    css_urls = self.css.get_css_uri_list(sample_html, base_url)

    # Verify the output
    expected_urls = [
    "http://example.com/styles/full_url_style.css",
    "http://example.com/styles/relative_url_style.css"
    ]
    self.assertListEqual(expected_urls, css_urls)

    依赖注入(inject)模拟

    现在,不太明显的事情就是对 getContent()类的 core.HttpRequests方法进行单元测试。我想您正在使用HTTP库,而不是在TCP套接字之上发出自己的请求。

    为了将测试保持在单元级别,您不希望通过电线发送任何内容。为了避免这种情况,您可以做一些测试,以确保正确使用HTTP库。这不是测试代码的行为,而是测试代码与代码周围其他对象的交互方式。

    一种方法是显式地声明对该库的依赖:我们可以在 HttpRequests.__init__中添加一个参数,以将其传递给库的HTTP客户端实例。假设我使用的HTTP库提供了 HttpClient对象,我们可以在该对象上调用 get()。您可以执行以下操作:
    class HttpRequests(object):

    def __init__(self, http_client):
    self.http_client = http_client

    def get_content(self, url):
    # You could imagine doing more complicated stuff here, like checking the
    # response code, or wrapping your library exceptions or whatever
    return self.http_client.get(url)

    我们已经明确表明了依赖关系,现在 HttpRequests的调用者需要满足该要求:这称为依赖关系注入(inject)(DI)。

    DI对于两件事非常有用:
  • 它避免了您的代码 secret 地依赖某个对象存在于
  • 某处的意外情况
  • 它允许编写测试,以根据该测试的目标来注入(inject)不同种类的对象

    在这里,我们可以使用将提供给core.HttpRequests的模拟对象,并且该对象将在不知不觉中使用,就好像它是真实库一样。之后,我们可以测试该交互是否按预期进行。
    import core

    class HttpRequestsTestCase(unittest.TestCase):

    def test_get_content_should_use_get_properly(self):
    # Setup

    url = "http://example.com"

    # We create an object that is not a real HttpClient but that will have
    # the same interface (see the `spec` argument). This mock object will
    # also have some nice methods and attributes to help us test how it was used.
    mock_http_client = Mock(spec=somehttplib.HttpClient)

    # Exercise

    http_requests = core.HttpRequests(mock_http_client)
    content = http_requests.get_content(url)

    # Here, the `http_client` attribute of `http_requests` is the mock object we
    # have passed it, so the method that is called is `mock.get()`, and the call
    # stops in the mock framework, without a real HTTP request being sent.

    # Verify

    # We expect our get_content method to have called our http library.
    # Let's check!
    mock_http_client.get.assert_called_with(url)

    # We can find out what our mock object has returned when get() was
    # called on it
    expected_content = mock_http_client.get.return_value
    # Since our get_content returns the same result without modification,
    # we should have received it
    self.assertEqual(content, expected_content)

    现在,我们已经测试了get_content方法是否与我们的HTTP库正确交互。我们已经定义了HttpRequests对象的边界并对其进行了测试,这是我们应该在单元测试级别进行的工作。现在,该请求已由该库处理,并且测试该库是否按预期工作肯定不是我们的单元测试套件的职责。

    猴子修补

    现在想象我们决定使用伟大的requests library。它的API具有更多的过程性,它没有提供我们可以抓取以发出HTTP请求的对象。相反,我们将导入模块并调用其get方法。

    然后,我们在HttpRequests中的core.py类将如下所示:
    import requests

    class HttpRequests(object):

    # No more DI in __init__

    def get_content(self, url):
    # We simply delegate the HTTP work to the `requests` module
    return requests.get(url)

    不再需要直接投资,所以现在,我们想知道:
  • 如何防止网络交互发生?
  • 如何测试我是否正确使用了requests模块?

  • 在这里,您可以使用动态语言提供的另一种奇妙但有争议的机制: monkey patching。在运行时,我们将 requests模块替换为我们制作并可以在测试中使用的对象。

    然后,我们的单元测试将类似于:
    import core

    class HttpRequestsTestCase(unittest.TestCase):

    def setUp(self):
    # We create a mock to replace the `requests` module
    self.mock_requests = Mock()

    # We keep a reference to the current, real, module
    self.old_requests = core.requests

    # We replace the module with our mock
    core.requests = self.mock_requests

    def tearDown(self):
    # It is very important that each unit test be isolated, so we need
    # to be good citizen and clean up after ourselves. This means that
    # we need to put back the correct `requests` module where it was
    core.requests = self.old_requests

    def test_get_content_should_use_get_properly(self):
    # Setup

    url = "http://example.com"

    # Exercise
    http_client = core.HttpRequests()
    content = http_client.get_content(url)

    # Verify

    # We expect our get_content method to have called our http library.
    # Let's check!
    self.mock_requests.get.assert_called_with(url)

    # We can find out what our mock object has returned when get() was
    # called on it
    expected_content = self.mock_requests.get.return_value
    # Since our get_content returns the same result without modification,
    # we should have received
    self.assertEqual(content, expected_content)

    为了使此过程不再那么冗长, mock模块具有一个 patch装饰器,用于装饰脚手架。然后我们只需要写:
    import core

    class HttpRequestsTestCase(unittest.TestCase):

    @patch("core.requests")
    def test_get_content_should_use_get_properly(self, mock_requests):
    # Notice the extra param in the test. This is the instance of `Mock` that the
    # decorator has substituted for us and it is populated automatically.

    ...

    # The param is now the object we need to make our assertions against
    expected_content = mock_requests.get.return_value

    结论

    使单元测试保持小巧,简单,快速和独立是非常重要的。依赖于另一台服务器运行的单元测试根本不是单元测试。为此,DI是一个很好的实践,而模拟对象是一个很好的工具。

    首先,要了解模拟的概念以及如何使用它们并不容易。像每个电动工具一样,它们也可能在您手中爆炸,例如使您相信自己已经测试过某些东西,而实际上却没有。确保模拟对象的行为和输入/输出反射(reflect)现实是至关重要的。

    附言

    鉴于我们从未在单元测试级别与真正的HTTP服务器进行交互,因此编写集成测试非常重要,以确保我们的应用程序能够与将在现实生活中处理的服务器进行对话。我们可以使用专门为集成测试设置的成熟服务器来做到这一点,或者编写人为的服务器。

    关于python - 如何在Python的单元测试方案中模拟HTTP请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11399148/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com