如何莫克在Python中的单元测试场景的HTTP请求(How to Mock an HTTP req

2019-06-26 03:26发布

我想包括我所有的测试涉及到HTTP Web服务器。 它并不需要很复杂的。 我宁愿不依赖的是上网。 所以,我可以测试我的程序的一些选项。

  1. 启动服务器
  2. 创建具有适当MIME类型,响应码等几个资源(URI)
  3. 运行测试(会很好不必从头开始每一个测试服务器太)
  4. 关闭服务器。

这些代码的任何提示将是有益的。 我试着用BaseHTTPServer的几件事,但没有成功。 nosetests命令似乎无限期地等待。

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

眼下,代码所依赖的网络服务器上。 它不应该。 我希望能够添加不同类型的样式表的组合的很多,后来测试协议,然后在他们的分析,组合等一些选项

Answer 1:

开始进行单元测试Web服务器绝对不是一个好的做法。 单元测试应该是简单的和孤立的,这意味着它们应该避免例如执行IO操作。

如果你想写什么是真正的单元测试,那么你应该制定自己的测试输入,也考虑模仿对象 。 蟒是一个动态语言,嘲讽和猴子寻路是简单而强大的编写单元测试工具。 特别是,看看优秀的模拟模块 。

简单的单元测试

所以,如果我们看一下你的CssTests例如,你想测试css.getCssUriList能够提取所有在一块HTML的,你给它引用的CSS样式表。 你在做什么在这个特定的单元测试不测试,您可以发送一个请求,并得到来自网站的响应,对不对? 你只是想确保给予一定的HTML,你的函数返回CSS网址的正确列表。 所以,在这个测试中,你显然不需要跟一个真正的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)    

与依赖注入惩戒

现在,一些不太明显的是单元测试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 :这就是所谓的依赖注入(DI)。

DI是两件事情非常有用:

  1. 它避免了意外,在您的代码secretely依靠一些物体上的某个地方
  2. 它允许测试要写入注入不同种类的对象取决于测试的目标

在这里,我们可以使用一个模拟对象,我们将给予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对象,并测试了它们,这是因为据我们应该在单元测试级别去。 请求现在在该库的手,这肯定不是我们的单元测试套件来测试该库按预期工作中的作用。

猴子打补丁

现在想象一下,我们决定使用大请求库 。 它的API更加程序化,它不存在的对象,我们可以抓住从发出HTTP请求。 相反,我们会导入模块,并调用它get方法。

我们HttpRequestscore.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)

没有更多的DI,所以现在,我们在想:

  • 我怎么防止发生网络互动?
  • 我怎么测试我使用requests正确模块?

在这里,您可以使用一个很棒的,但有争议的,机制,动态语言提供: 猴子修补 。 我们将更换,在运行时,该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是一个伟大的实践,模拟对象一个伟大的工具。

首先,它是不容易得到你的头周围的模拟概念,以及如何虽然使用它们。 像每一个电动工具,他们也可以在爆炸你的手和例如让你相信你已经测试过的东西时,在现实中你没有。 确保模拟对象的行为和输入/输出反映现实的情况是最重要的。

PS

鉴于我们从来没有与在单元测试级别真正的HTTP服务器交互,它写的集成测试,将确保我们的应用程序是谈得来的那种服务器,它会在现实生活中处理是非常重要的。 我们可以与集成测试专门设立一个完全成熟的服务器做到这一点,或者写一个做作的一个。



文章来源: How to Mock an HTTP request in a unit testing scenario in Python