Как макетировать данные как request.Response типа в Python

1

Я хотел бы написать несколько тестовых примеров для реализации object_check в логике isinstance (obj, requests.Response). После того, как я создаю данные Mock как возвращаемое значение для request.post. Тип для данных mock всегда является классом Mock. Таким образом, как я могу переписать макет данных, поэтому макет данных может быть типом запросов. Ответ? поэтому я могу d = obj.json() линию d = obj.json()?

from unittest.mock import patch, Mock
import unittest
import requests
from requests.exceptions import HTTPError
import pytest
def object_check(obj):
    if isinstance(obj, bytes):
        d = ujson.decode(obj.decode())
    elif isinstance(obj, requests.Response):
        d = obj.json()
    else:
        raise ValueError('invalid type')
    return d

def service_post(params):
    """
    trivial function that does a GET request
    against google, checks the status of the
    result and returns the raw content
    """
    url = "https://www.iamdomain.com"
    params = {'number': 1234, 'user_id': 1, 'name': 'john'}
    resp = requests.post(url, data=params)
    return object_check(resp)

@patch.object(requests, 'post')
def test_service_post(mock_request_post):
    data = {'number': 0000, 'user_id': 0, 'name': 'john'}
    def res():
        r = Mock()
        r.status_code.return_value = 200
        r.json.return_value = data
        return r
    mock_request_post.return_value = res()
    assert data == service_post(data)
Теги:
python-3.x
python-unittest
pytest

2 ответа

2
Лучший ответ

Вы можете сделать это:

@patch.object(requests, 'post')
def test_service_post(mock_request_post):
    data = {'number': 0000, 'user_id': 0, 'name': 'john'}
    def res():
        r = requests.Response()
        r.status_code = 200
        def json_func():
            return data
        r.json = json_func
        return r
    mock_request_post.return_value = res()
    assert data == service_post(data)

Затем тест проходил для меня, когда я запускал его локально. Имейте в виду, что Mock - это мини-запах.

Раньше я был большим поклонником Mock. Однако, поскольку я вырос как разработчик, я действительно стараюсь избегать этого. Это может обмануть вас в действительно плохой дизайн, и их может быть очень сложно поддерживать (тем более, что вы изменяете свой Mock чтобы удерживать возвращаемые значения). Mock также может создать ложное чувство безопасности (ваш тест будет продолжать проходить, даже если веб-службы резко меняются, поэтому вы можете взорваться в prod). Я не думаю, что вам это действительно нужно. Две альтернативы:

  1. Вы можете поразить любую услугу, которую вы пытаетесь ударить, и сериализовать (сохранить) этот ответ с помощью pickle и сохранить на диск (сохранить его в своем тестовом наборе). Затем проверьте, чтобы ваш модуль тестировал его и использовал фактический объект ответа. Вам все равно придется patch на requests.post, но по крайней мере возвращаемые значения будут выстроены для вас, и вам не придется добавлять или изменять их по мере роста потребностей/приложений.
  2. Просто попал в Интернет. Забудьте об patch полностью: просто выполните POST в своем тесте и проверьте ответ. Конечно, это может быть медленным и работать будет только в том случае, если у вас есть интернет. И вы получите пугающих пуристов, которые скажут вам, чтобы вы никогда не делали этого в единичном тесте. Может быть, переместите его на тест интеграции, если вы столкнетесь с одним из этих пуристых людей. Но серьезно, нет никакой замены для того, чтобы делать то, что вы на самом деле собираетесь делать в prod. Положительным моментом является то, что если веб-сервис изменится, вы сразу узнаете об этом и сможете исправить свой код. Недостатком является то, что он может замедлить ваш тестовый пакет, и это потенциально ненадежный тест (если веб-сервис не работает, ваш тест не удастся... но на самом деле было бы хорошо это знать).

Я рекомендую, если веб-служба нестабильна (т.е. Может измениться), используйте опцию 2. Else, используйте опцию 1. Или сделайте некоторую комбинацию обоих (Mock и patch для модульного теста и нажмите сервис в тесте интеграции). Только вы можете решить!

HTH, удачи!

  • 0
    спасибо .. это отличное предложение. для варианта 1, что является лучшим способом для меня "сериализовать (сохранить) этот ответ с рассолом"? как ваш пример кода?
  • 0
    pickle.dump(your_object, your_file_descriptor) и pickle.load("your_filename") . Вы можете увидеть больше здесь: stackoverflow.com/questions/4530611/… и документы для pickle здесь: docs.python.org/3/library/pickle.html . Вы действительно захотите включить elif isinstance(obj, requests.Response): , а затем вставить логику дампа сразу после elif isinstance(obj, requests.Response): Точно так же, когда вы делаете тест, вы читаете эти requests.Response объект обратно с диска и передаете его везде, где вы хотите запустить модульные тесты, которые нуждаются в нем как в зависимости.
Показать ещё 2 комментария
1

Используйте аргумент spec при создании экземпляра макета:

>>> from unittest.mock import Mock
>>> from requests import Response
>>> m = Mock(spec=Response)
>>> m.__class__
requests.models.Response
>>> isinstance(m, Response)
True

Также обратите внимание, что r.status_code.return_value = 200 не будет работать с speccing; вместо этого установите значение:

r.status_code = 200
  • 0
    но isinstance(m, request.Response) == Ложь? это мой вопрос
  • 0
    Я не уверен, что твой вопрос. Нет таких классов requests.Response (кроме request.Response вы написали; я предполагаю, что это только опечатка). Существует класс requests.models.Response , который экспортируется в requests.__init__.py через __all__ . Таким образом, isinstance(m, requests.Response) вернет True .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню