单元测试使用请求库的 python 应用程序

我正在编写一个使用 Kenneth Reitz 的 请求库执行 REST 操作的应用程序,我正在努力寻找一种很好的方法来对这些应用程序进行单元测试,因为请求通过模块级方法提供它的方法。

我想要的是能够综合两方之间的对话; 提供一系列请求断言和响应。

60716 次浏览

您可以使用诸如 嘲笑者之类的模仿库来拦截对请求库的调用并返回指定的结果。

作为一个非常简单的示例,考虑使用请求库的这个类:

class MyReq(object):
def doSomething(self):
r = requests.get('https://api.github.com', auth=('user', 'pass'))
return r.headers['content-type']

下面是一个单元测试,它拦截对 requests.get的调用并返回指定的测试结果:

import unittest
import requests
import myreq


from mocker import Mocker, MockerTestCase


class MyReqTests(MockerTestCase):
def testSomething(self):
# Create a mock result for the requests.get call
result = self.mocker.mock()
result.headers
self.mocker.result({'content-type': 'mytest/pass'})


# Use mocker to intercept the call to requests.get
myget = self.mocker.replace("requests.get")
myget('https://api.github.com', auth=('user', 'pass'))
self.mocker.result(result)


self.mocker.replay()


# Now execute my code
r = myreq.MyReq()
v = r.doSomething()


# and verify the results
self.assertEqual(v, 'mytest/pass')
self.mocker.verify()


if __name__ == '__main__':
unittest.main()

当我运行这个单元测试时,我得到以下结果:

.
----------------------------------------------------------------------
Ran 1 test in 0.004s


OK

就像 Srgerg 的回答:

def replacer(method, endpoint, json_string):
from mocker import Mocker, ANY, CONTAINS
mocker = Mocker()
result = mocker.mock()
result.json()
mocker.count(1, None)
mocker.result(json_string)
replacement = mocker.replace("requests." + method)
replacement(CONTAINS(endpoint), params=ANY)
self.mocker.result(result)
self.mocker.replay()

对于请求库,这将通过方法和要访问的端点拦截请求,并替换。在传入 json _ string 的响应中使用 json ()。

如果您使用特定的请求,请尝试 Httmock。它非常简单和优雅:

from httmock import urlmatch, HTTMock
import requests


# define matcher:
@urlmatch(netloc=r'(.*\.)?google\.com$')
def google_mock(url, request):
return 'Feeling lucky, punk?'


# open context to patch
with HTTMock(google_mock):
# call requests
r = requests.get('http://google.com/')
print r.content  # 'Feeling lucky, punk?'

如果你想要一些更通用的东西(例如,模仿任何打 http 电话的库) ,那就用 httpretty吧。

几乎同样优雅:

import requests
import httpretty


@httpretty.activate
def test_one():
# define your patch:
httpretty.register_uri(httpretty.GET, "http://yipit.com/",
body="Find the best daily deals")
# use!
response = requests.get('http://yipit.com')
assert response.text == "Find the best daily deals"

HTTpretty 的功能更加丰富——它还提供了模仿状态代码、流式响应、旋转响应、动态响应(带回调)。

实际上,有点奇怪的是,这个库有一个关于最终用户单元测试的空白页面,而目标是用户友好性和易用性。然而 Dropbox 提供了一个易于使用的库,不出所料称为 responses。这是它的 介绍文章。它说,他们未能使用 httpretty,但没有说明失败的原因,并编写了一个具有类似 API 的库。

import unittest


import requests
import responses




class TestCase(unittest.TestCase):


@responses.activate
def testExample(self):
responses.add(**{
'method'         : responses.GET,
'url'            : 'http://example.com/api/123',
'body'           : '{"error": "reason"}',
'status'         : 404,
'content_type'   : 'application/json',
'adding_headers' : {'X-Foo': 'Bar'}
})


response = requests.get('http://example.com/api/123')


self.assertEqual({'error': 'reason'}, response.json())
self.assertEqual(404, response.status_code)

这些答案中缺少的是 请求-模仿

从他们的页面:

>>> import requests
>>> import requests_mock

作为上下文管理者:

>>> with requests_mock.mock() as m:


...     m.get('http://test.com', text='data')
...     requests.get('http://test.com').text
...
'data'

或者作为一个室内设计师:

>>> @requests_mock.mock()
... def test_func(m):
...     m.get('http://test.com', text='data')
...     return requests.get('http://test.com').text
...
>>> test_func()
'data'

如果将响应处理程序/解析器划分为单独的函数,则可以直接使用 requests.Response objects,而无需模拟客户机-服务器交互。

密码正在测试中

from xml.dom import minidom
from requests.models import Response


def function_under_test(s3_response: Response):
doc = minidom.parseString(s3_response.text)


return (
s3_response.status_code,
doc.getElementsByTagName('Code').item(0).firstChild.data,
)

测试代码

import unittest
from io import BytesIO


class Test(unittest.TestCase):


def test_it(self):
s3_response = Response()
s3_response.status_code = 404
s3_response.raw = BytesIO(b"""<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The resource you requested does not exist</Message>
<Resource>/mybucket/myfoto.jpg</Resource>
<RequestId>4442587FB7D0A2F9</RequestId>
</Error>
""")


parsed_response = function_under_test(s3_response)


self.assertEqual(404, parsed_response[0])
self.assertEqual("NoSuchKey", parsed_response[1])