요청 및 응답을 모의하려면 어떻게 해야 합니까?
나는 피톤스를 조롱하기 위해 피톤스의 모의 패키지를 사용하려고 한다.requests
듈 scenario 입니까?아래 시나리오에서 작업하기 위한 기본적인 전화는 무엇입니까?
내 views.py에는 매번 다른 응답으로 다양한 requests.get() 호출을 하는 함수가 있다.
def myview(request):
res1 = requests.get('aurl')
res2 = request.get('burl')
res3 = request.get('curl')
테스트 수업에서 이런 것을 하고 싶지만 정확한 메서드 호출을 알 수 없습니다.
순서 1:
# Mock the requests module
# when mockedRequests.get('aurl') is called then return 'a response'
# when mockedRequests.get('burl') is called then return 'b response'
# when mockedRequests.get('curl') is called then return 'c response'
순서 2:
내 견해를 묻다
순서 3:
응답에 'a response', 'b response', 'c response'가 포함되어 있는지 확인합니다.
스텝 1(요청 모듈 Mocking)을 완료하려면 어떻게 해야 합니까?
다음과 같이 실행할 수 있습니다(이 파일을 그대로 실행할 수 있습니다).
import requests
import unittest
from unittest import mock
# This is the class we want to test
class MyGreatClass:
def fetch_json(self, url):
response = requests.get(url)
return response.json()
# This method will be used by the mock to replace requests.get
def mocked_requests_get(*args, **kwargs):
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
if args[0] == 'http://someurl.com/test.json':
return MockResponse({"key1": "value1"}, 200)
elif args[0] == 'http://someotherurl.com/anothertest.json':
return MockResponse({"key2": "value2"}, 200)
return MockResponse(None, 404)
# Our test case class
class MyGreatClassTestCase(unittest.TestCase):
# We patch 'requests.get' with our own method. The mock object is passed in to our test case method.
@mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
# Assert requests.get calls
mgc = MyGreatClass()
json_data = mgc.fetch_json('http://someurl.com/test.json')
self.assertEqual(json_data, {"key1": "value1"})
json_data = mgc.fetch_json('http://someotherurl.com/anothertest.json')
self.assertEqual(json_data, {"key2": "value2"})
json_data = mgc.fetch_json('http://nonexistenturl.com/cantfindme.json')
self.assertIsNone(json_data)
# We can even assert that our mocked method was called with the right parameters
self.assertIn(mock.call('http://someurl.com/test.json'), mock_get.call_args_list)
self.assertIn(mock.call('http://someotherurl.com/anothertest.json'), mock_get.call_args_list)
self.assertEqual(len(mock_get.call_args_list), 3)
if __name__ == '__main__':
unittest.main()
중요사항:만약 당신이MyGreatClass
는 다른 패키지에 . 예를 들어, 클래스는 패키지에 살고 있습니다.my.great.package
my.great.package.requests.get
이 'request.'은 다음과 같습니다
import unittest
from unittest import mock
from my.great.package import MyGreatClass
# This method will be used by the mock to replace requests.get
def mocked_requests_get(*args, **kwargs):
# Same as above
class MyGreatClassTestCase(unittest.TestCase):
# Now we must patch 'my.great.package.requests.get'
@mock.patch('my.great.package.requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
# Same as above
if __name__ == '__main__':
unittest.main()
맛있게 드세요!
응답 라이브러리를 사용해 보십시오.다음은 해당 문서의 예입니다.
import responses
import requests
@responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
json={'error': 'not found'}, status=404)
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert responses.calls[0].response.text == '{"error": "not found"}'
자신을 조롱하는 것보다 훨씬 편리합니다.
HTTPretty도 있습니다.
않다requests
어떤 면에서는 라이브러리가 더 강력하다는 것을 알게 되었습니다만, 그것이 대행 수신한 요구를 검사하는 데는 그다지 도움이 되지 않습니다.responses
할 수 있다
httmock도 있어요.
도서관보다 를 끌고 있는 새로운 requests
는 비동기용 퍼스트클래스 지원을 추가합니다.httpx의 모의 라이브러리는 다음과 같습니다.https://github.com/lundberg/respx
다음과 같은 이점이 있습니다.
import mock
@mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
개별 모듈의 테스트 작성에 requests-mock을 사용했습니다.
# module.py
import requests
class A():
def get_response(self, url):
response = requests.get(url)
return response.text
테스트 결과:
# tests.py
import requests_mock
import unittest
from module import A
class TestAPI(unittest.TestCase):
@requests_mock.mock()
def test_get_response(self, m):
a = A()
m.get('http://aurl.com', text='a response')
self.assertEqual(a.get_response('http://aurl.com'), 'a response')
m.get('http://burl.com', text='b response')
self.assertEqual(a.get_response('http://burl.com'), 'b response')
m.get('http://curl.com', text='c response')
self.assertEqual(a.get_response('http://curl.com'), 'c response')
if __name__ == '__main__':
unittest.main()
requests.post을 조롱하고 http 메서드로 변경합니다.
@patch.object(requests, 'post')
def your_test_method(self, mockpost):
mockresponse = Mock()
mockpost.return_value = mockresponse
mockresponse.text = 'mock return'
#call your target method now
다음은 requests Response 클래스를 포함하는 솔루션입니다.더 깨끗한 IMHO입니다.
import json
from unittest.mock import patch
from requests.models import Response
def mocked_requests_get(*args, **kwargs):
response_content = None
request_url = kwargs.get('url', None)
if request_url == 'aurl':
response_content = json.dumps('a response')
elif request_url == 'burl':
response_content = json.dumps('b response')
elif request_url == 'curl':
response_content = json.dumps('c response')
response = Response()
response.status_code = 200
response._content = str.encode(response_content)
return response
@mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
response = requests.get(url='aurl')
assert ...
가짜 응답을 조롱하는 다른 방법은 다음과 같이 기본 HttpResponse 클래스의 인스턴스를 단순히 인스턴스화하는 것입니다.
from django.http.response import HttpResponseBase
self.fake_response = HttpResponseBase()
나는 요하네스 파헨크룩의 대답으로 시작했는데 그것은 나에게 매우 효과가 있었다.서드파티 리소스를 테스트하지 않고 애플리케이션을 격리하는 것이 목표이기 때문에 요청 라이브러리를 조롱해야 했습니다.
그 후 Python의 Mock 라이브러리에 대해 조금 더 읽었는데, MockResponse 클래스를 Python Mock 클래스로 대체할 수 있다는 것을 깨달았습니다. MockResponse 클래스는 'Test Double' 또는 'Fake'로 불릴 수 있습니다.
좋은 은 '할 수 있다'는죠.assert_called_with
,call_args
. 은 필요 없습니다.추가 라이브러리는 필요 없습니다.'더 많은 에, 할도 있고 않을 도 있다가독성'이나 '더 많은 비단뱀'과 같은 추가적인 이점들은 주관적이기 때문에, 그것들은 당신에게 역할을 할 수도 있고 하지 않을 수도 있다.
테스트 더블 대신 Python's Mock을 사용하여 업데이트한 내 버전은 다음과 같습니다.
import json
import requests
from unittest import mock
# defube stubs
AUTH_TOKEN = '{"prop": "value"}'
LIST_OF_WIDGETS = '{"widgets": ["widget1", "widget2"]}'
PURCHASED_WIDGETS = '{"widgets": ["purchased_widget"]}'
# exception class when an unknown URL is mocked
class MockNotSupported(Exception):
pass
# factory method that cranks out the Mocks
def mock_requests_factory(response_stub: str, status_code: int = 200):
return mock.Mock(**{
'json.return_value': json.loads(response_stub),
'text.return_value': response_stub,
'status_code': status_code,
'ok': status_code == 200
})
# side effect mock function
def mock_requests_post(*args, **kwargs):
if args[0].endswith('/api/v1/get_auth_token'):
return mock_requests_factory(AUTH_TOKEN)
elif args[0].endswith('/api/v1/get_widgets'):
return mock_requests_factory(LIST_OF_WIDGETS)
elif args[0].endswith('/api/v1/purchased_widgets'):
return mock_requests_factory(PURCHASED_WIDGETS)
raise MockNotSupported
# patch requests.post and run tests
with mock.patch('requests.post') as requests_post_mock:
requests_post_mock.side_effect = mock_requests_post
response = requests.post('https://myserver/api/v1/get_widgets')
assert response.ok is True
assert response.status_code == 200
assert 'widgets' in response.json()
# now I can also do this
requests_post_mock.assert_called_with('https://myserver/api/v1/get_widgets')
Repl.it 링크:
https://repl.it/@abkonsta/Using-unittestMock-for-requestspost#main.py
https://repl.it/@abkonsta/Using-test-double-for-request #main.py
저는 아직 복잡한 테스트를 많이 해보지 않았지만, 이 테스트는 성공했습니다.
import json
from requests import Response
class MockResponse(Response):
def __init__(self,
url='http://example.com',
headers={'Content-Type':'text/html; charset=UTF-8'},
status_code=200,
reason = 'Success',
_content = 'Some html goes here',
json_ = None,
encoding='UTF-8'
):
self.url = url
self.headers = headers
if json_ and headers['Content-Type'] == 'application/json':
self._content = json.dumps(json_).encode(encoding)
else:
self._content = _content.encode(encoding)
self.status_code = status_code
self.reason = reason
self.encoding = encoding
다음으로 응답을 작성할 수 있습니다.
mock_response = MockResponse(
headers={'Content-Type' :'application/json'},
status_code=401,
json_={'success': False},
reason='Unauthorized'
)
mock_response.raise_for_status()
주다
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: http://example.com
요청에 대처하는 방법 중 하나는 라이브러리 베타맥스를 사용하는 것입니다.모든 요구를 기록하고 그 후 동일한 파라미터로 동일한 URL에 요청을 하면 베타맥스가 기록된 요청을 사용하게 됩니다.저는 웹 크롤러를 테스트하기 위해 이 기능을 사용하여 시간을 대폭 절약할 수 있습니다.
import os
import requests
from betamax import Betamax
from betamax_serializers import pretty_json
WORKERS_DIR = os.path.dirname(os.path.abspath(__file__))
CASSETTES_DIR = os.path.join(WORKERS_DIR, u'resources', u'cassettes')
MATCH_REQUESTS_ON = [u'method', u'uri', u'path', u'query']
Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
with Betamax.configure() as config:
config.cassette_library_dir = CASSETTES_DIR
config.default_cassette_options[u'serialize_with'] = u'prettyjson'
config.default_cassette_options[u'match_requests_on'] = MATCH_REQUESTS_ON
config.default_cassette_options[u'preserve_exact_body_bytes'] = True
class WorkerCertidaoTRT2:
session = requests.session()
def make_request(self, input_json):
with Betamax(self.session) as vcr:
vcr.use_cassette(u'google')
response = session.get('http://www.google.com')
https://betamax.readthedocs.io/en/latest/
대신 requests-mock을 사용할 수 있습니까?
이 대신 myview를 .requests.Session
오브젝트, 오브젝트 요구 및 출력에 대한 조작을 실시합니다.
# mypackage.py
def myview(session):
res1 = session.get("http://aurl")
res2 = session.get("http://burl")
res3 = session.get("http://curl")
return f"{res1.text}, {res2.text}, {res3.text}"
# test_myview.py
from mypackage import myview
import requests
def test_myview(requests_mock):
# set up requests
a_req = requests_mock.get("http://aurl", text="a response")
b_req = requests_mock.get("http://burl", text="b response")
c_req = requests_mock.get("http://curl", text="c response")
# test myview behaviour
session = requests.Session()
assert myview(session) == "a response, b response, c response"
# check that requests weren't called repeatedly
assert a_req.called_once
assert b_req.called_once
assert c_req.called_once
assert requests_mock.call_count == 3
이 경우에도 하실 수 있습니다.requests_mock
Pytest 이 py py py py py py py py py py py py py py py 。설명서는 훌륭합니다.
비동기 API 호출을 어떻게 모킹해야 할지 고민했으므로 이 정보를 추가하겠습니다.
비동기 콜을 조롱하기 위해 다음과 같이 했습니다.
테스트하고 싶었던 기능은 다음과 같습니다.
async def get_user_info(headers, payload):
return await httpx.AsyncClient().post(URI, json=payload, headers=headers)
아직 MockResponse 클래스가 필요합니다.
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
MockResponseAsync 클래스를 추가합니다.
class MockResponseAsync:
def __init__(self, json_data, status_code):
self.response = MockResponse(json_data, status_code)
async def getResponse(self):
return self.response
여기 테스트가 있습니다.여기서 중요한 것은 init 함수는 비동기일 수 없고 getResponse 호출은 비동기이기 때문에 모두 체크 아웃되기 전에 응답을 작성하는 것입니다.
@pytest.mark.asyncio
@patch('httpx.AsyncClient')
async def test_get_user_info_valid(self, mock_post):
"""test_get_user_info_valid"""
# Given
token_bd = "abc"
username = "bob"
payload = {
'USERNAME': username,
'DBNAME': 'TEST'
}
headers = {
'Authorization': 'Bearer ' + token_bd,
'Content-Type': 'application/json'
}
async_response = MockResponseAsync("", 200)
mock_post.return_value.post.return_value = async_response.getResponse()
# When
await api_bd.get_user_info(headers, payload)
# Then
mock_post.return_value.post.assert_called_once_with(
URI, json=payload, headers=headers)
더 좋은 방법이 있다면 말씀해주세요. 하지만 저는 그게 꽤 깨끗하다고 생각해요.
지금까지 가장 간단한 방법:
from unittest import TestCase
from unittest.mock import Mock, patch
from .utils import method_foo
class TestFoo(TestCase):
@patch.object(utils_requests, "post") # change to desired method here
def test_foo(self, mock_requests_post):
# EXPLANATION: mocked 'post' method above will return some built-in mock,
# and its method 'json' will return mock 'mock_data',
# which got argument 'return_value' with our data to be returned
mock_data = Mock(return_value=[{"id": 1}, {"id": 2}])
mock_requests_post.return_value.json = mock_data
method_foo()
# TODO: asserts here
"""
Example of method that you can test in utils.py
"""
def method_foo():
response = requests.post("http://example.com")
records = response.json()
for record in records:
print(record.get("id"))
# do other stuff here
pytest를 위해 추가 libs를 설치하고 싶지 않은 분들을 위한 예가 있습니다.위의 예에 따라 여기에 확장자를 붙여 복제합니다.
import datetime
import requests
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
self.elapsed = datetime.timedelta(seconds=1)
# mock json() method always returns a specific testing dictionary
def json(self):
return self.json_data
def test_get_json(monkeypatch):
# Any arguments may be passed and mock_get() will always return our
# mocked object, which only has the .json() method.
def mock_get(*args, **kwargs):
return MockResponse({'mock_key': 'mock_value'}, 418)
# apply the monkeypatch for requests.get to mock_get
monkeypatch.setattr(requests, 'get', mock_get)
# app.get_json, which contains requests.get, uses the monkeypatch
response = requests.get('https://fakeurl')
response_json = response.json()
assert response_json['mock_key'] == 'mock_value'
assert response.status_code == 418
assert response.elapsed.total_seconds() == 1
============================= test session starts ==============================
collecting ... collected 1 item
test_so.py::test_get_json PASSED [100%]
============================== 1 passed in 0.07s ===============================
urlib 또는 urlib2/urlib3에서 요청으로 변환하고 응답을 조롱하려고 하는 등 여전히 어려움을 겪고 있는 사용자에게 유용한 힌트입니다. 모의 실행 시 다음과 같은 오류가 발생했습니다.
with requests.get(path, auth=HTTPBasicAuth('user', 'pass'), verify=False) as url:
Attribute Error: __enter__
음, 물론, 만약 내가 어떻게 하면with
(나는 하지 않았다), 나는 그것이 (PE 343의) 잔재적이고 불필요한 컨텍스트라는 것을 알았다.요청 라이브러리를 사용할 때는 기본적으로 후드 아래에서 동일한 작업을 수행하므로 필요하지 않습니다.삭제만 하면 됩니다.with
베어 사용requests.get(...)
밥은 네 삼촌이고
pytest 사용자에게는 https://pypi.org/project/pytest-responsemock/에서 제공하는 편리한 설치 프로그램이 있습니다.
예를 들어 GET을 http://some.domain에 모의하는 경우 다음을 수행할 수 있습니다.
def test_me(response_mock):
with response_mock('GET http://some.domain -> 200 :Nice'):
response = send_request()
assert result.ok
assert result.content == b'Nice'
실제 요청을 동일한 데이터를 반환하는 가짜 요청과 교환하여 실제 외부 라이브러리에서 프로그래밍 로직을 분리하는 방법을 시연합니다.외부 API 호출 시 이 프로세스가 최선입니다.
import pytest
from unittest.mock import patch
from django.test import RequestFactory
@patch("path(projectname.appname.filename).requests.post")
def test_mock_response(self, mock_get, rf: RequestFactory):
mock_get.return_value.ok = Mock(ok=True)
mock_get.return_value.status_code = 400
mock_get.return_value.json.return_value = {you can define here dummy response}
request = rf.post("test/", data=self.payload)
response = view_name_view(request)
expected_response = {
"success": False,
"status": "unsuccessful",
}
assert response.data == expected_response
assert response.status_code == 400
사용.requests_mock
어떤 요구에도 쉽게 패치를 적용할 수 있습니다.
pip install requests-mock
from unittest import TestCase
import requests_mock
from <yourmodule> import <method> (auth)
class TestApi(TestCase):
@requests_mock.Mocker()
def test_01_authentication(self, m):
"""Successful authentication using username password"""
token = 'token'
m.post(f'http://localhost/auth', json= {'token': token})
act_token =auth("user", "pass")
self.assertEqual(act_token, token)
언급URL : https://stackoverflow.com/questions/15753390/how-can-i-mock-requests-and-the-response
'programing' 카테고리의 다른 글
Java에 Mutex가 있나요? (0) | 2022.09.18 |
---|---|
시스템에 설치되어 있는 TensorFlow 버전을 확인하려면 어떻게 해야 합니까? (0) | 2022.09.18 |
HTML5 localStorage에 개체를 저장하는 방법 (0) | 2022.09.18 |
MySQL에서 필드가 null인지 빈지 확인하는 방법 (0) | 2022.09.18 |
문자열에 제로를 채우다 (0) | 2022.09.18 |