栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

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

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

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

简单的单元测试

因此,如果我们看一下您的

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

我将执行以下操作:

import unittestclass 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 pre, or wrapping your library exceptions or whatever        return self.http_client.get(url)

我们已经明确了依赖关系,并且调用者现在必须满足该要求

HttpRequests
:这称为依赖关系注入(DI)。

DI对于两件事非常有用:

  1. 它避免了您的代码秘密地依赖某个对象存在于某处的意外情况
  2. 它允许编写根据测试目标注入不同种类对象的测试

在这里,我们可以使用我们将提供给

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

import coreclass 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
方法。

然后,我们的

HttpRequests
班级
core.py
看起来将如下所示:

import requestsclass 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
    是否正确使用了该模块?

在这里,您可以使用动态语言提供的另一种奇妙但有争议的机制:猴子补丁。我们将在运行时将

requests
模块替换为我们制作并可以在测试中使用的对象。

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

import coreclass 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 coreclass 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服务器进行交互,因此编写集成测试非常重要,以确保我们的应用程序能够与将在现实生活中处理的服务器进行对话。我们可以使用专门为集成测试设置的成熟服务器来做到这一点,或者编写人为的服务器。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/641432.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号