programing

요청 및 응답을 모의하려면 어떻게 해야 합니까?

shortcode 2022. 9. 18. 20:36
반응형

요청 및 응답을 모의하려면 어떻게 해야 합니까?

나는 피톤스를 조롱하기 위해 피톤스의 모의 패키지를 사용하려고 한다.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.packagemy.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_mockPytest 이 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

반응형