首页 > 解决方案 > 如何模拟引发 urllib 错误

问题描述

在python 文档中阅读此内容后,我发现了(通过调用)可以引发HTTPErrorURLError异常:get_response_from_external_apimake_request_and_get_responseurlliburlopen

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方法时,我试图模拟引发HTTPErrorURLError异常

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(via get_response_from_external_api) 的返回值超出了 except-block 的范围。不知道我是否应该像这里urllib.urlopen.read看到的那样模拟整个?

标签: python-3.xmockingpytesturllibpytest-mock

解决方案


没有必要模拟部分urlopen——通过模拟你的函数来引发一个异常,你可以确保它urlopen不会被调用。

由于您正在创建这些异常以检查您的错误处理代码是否正常工作,因此它们不需要完整 - 它们只需要包含满足您的测试所需的最少信息。

HTTPError需要五个参数:

  • 一个网址
  • 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

最后,你需要颠倒你的 try except blocks 的顺序 -HTTPError是 的子类URLError,所以你需要先对其进行测试,否则它会被except URLError块处理。


推荐阅读