Как имитировать ошибки повышения urllib

Прочитав это в документах python, я улавливаюHTTPError а также URLError исключения в get_response_from_external_api что make_request_and_get_response (через urllibс urlopen колл) может поднять:

foo.main.py

from urllib.request import urlopen
import contextlib
from urllib.error import HTTPError, URLError

def make_request_and_get_response(q):
    with contextlib.closing(urlopen(q)) as response:
        return response.read()

def get_response_from_external_api(q):
    try:
        resp = make_request_and_get_response(q)
        return resp
    except URLError as e:
        print('Got a URLError: ', e)
    except HTTPError as e:
        print('Got a HTTPError: ', e)

if __name__ == "__main__":
    query = 'test'
    result = get_response_from_external_api(query)
    print(result)

При тестировании get_response_from_external_apiметод, я пытаюсь издеваться над повышениемHTTPError а также URLErrorисключения:

foo.test_main.py

from foo.main import get_response_from_external_api

import pytest
from unittest.mock import patch, Mock
from urllib.error import HTTPError, URLError

def test_get_response_from_external_api_with_httperror(capsys):
    with patch('foo.main.make_request_and_get_response') as mocked_method:
        with pytest.raises(HTTPError) as exc:
            mocked_method.side_effect = HTTPError()  # TypeError
            resp = get_response_from_external_api(mocked_method)

            out, err = capsys.readouterr()
            assert resp is None
            assert 'HTTPError' in out
            assert str(exc) == HTTPError

def test_get_response_from_external_api_with_urlerror(capsys):
    with patch('foo.main.make_request_and_get_response') as mocked_method:
        with pytest.raises(URLError) as exc:
            mocked_method.side_effect = URLError()  # TypeError
            resp = get_response_from_external_api(mocked_method)

            out, err = capsys.readouterr()
            assert resp is None
            assert 'URLError' in out
            assert str(exc) == URLError

Но я получаю TypeError: __init__() missing 5 required positional arguments: 'url', 'code', 'msg', 'hdrs', and 'fp'. Я новичок в синтаксисе имитаций Python и ищу примеры.

Я прочитал этот ответ, но не вижу, как это можно применить в моем случае, когда возвращаемое значениеurllib.urlopen (через get_response_from_external_api) выходит за рамки блока except. Не уверен, стоит ли вместо этого издеваться над всемurllib.urlopen.readа не как здесь?

1 ответ

Нет необходимости издеваться над частями urlopen - высмеивая вашу функцию для создания исключения, вы гарантируете, что urlopen не позвонят.

Поскольку вы создаете эти исключения, чтобы проверить, работает ли ваш код обработки ошибок, они не должны быть полными - они должны содержать только минимальную информацию, необходимую для удовлетворения ваших тестов.

HTTPError ожидает пять аргументов:

  • URL
  • код состояния HTTP
  • сообщение об ошибке
  • заголовки запроса
  • файловый объект (тело ответа)

Для насмешек все это может быть None, но может быть полезно создать объект, который выглядит как настоящая ошибка. Если что-то будет читать "файловый объект", вы можете передатьio.BytesIO экземпляр, содержащий пример ответа, но это не кажется необходимым, исходя из кода в вопросе.

>>> h = HTTPError('http://example.com', 500, 'Internal Error', {}, None)
>>> h
<HTTPError 500: 'Internal Error'>

URLError ожидает единственный аргумент, который может быть строкой или экземпляром исключения; для насмешек достаточно строки.

>>> u = URLError('Unknown host')
>>> u

URLError('Unknown host')

Вот код из вопроса, измененный с учетом вышеизложенного. И нет необходимости передавать фиктивную функцию самой себе - просто передайте произвольную строку. Я удалилwith pytest.raises блоков, потому что исключение фиксируется в блоках try / except вашего кода: вы проверяете, что ваш код обрабатывает исключение сам, а не то, что исключение распространяется до тестовой функции.

from foo.main import get_response_from_external_api

import pytest
from unittest.mock import patch, Mock
from urllib.error import HTTPError, URLError

def test_get_response_from_external_api_with_httperror(capsys):
    with patch('foo.main.make_request_and_get_response') as mocked_method:
        mocked_method.side_effect = HTTPError('http://example.com', 500, 'Internal Error', {}, None)
        resp = get_response_from_external_api('any string')
        assert resp is None
        out, err = capsys.readouterr()
        assert 'HTTPError' in out


def test_get_response_from_external_api_with_urlerror(capsys):
    with patch('foo.main.make_request_and_get_response') as mocked_method:
        mocked_method.side_effect = URLError('Unknown host')
        resp = get_response_from_external_api('any string')
        assert resp is None
        out, err = capsys.readouterr()
        assert 'URLError' in out

Наконец, вам нужно изменить порядок вашей попытки, кроме блоков - HTTPError является подклассом URLError, поэтому вам нужно сначала протестировать его, иначе он будет обработан except URLError блок.

Другие вопросы по тегам