1

注意:此问题基于我之前提出的问题,但根据此答案修改为不同。

使用side_effect,我试图在'URLError'调用模拟时引发异常,但我收到一个DID NOT RAISE我不理解的错误。

我有一个Query带有类方法的类,make_request_and_get_response它可以引发几个异常。我没有在 in 的方法中捕获'URLError'异常,所以我应该能够期望被引发,然后,它的异常被嘲笑。get_response_from_external_apimain.py

查询.py

from urllib.request import urlopen
import contextlib
import urllib

class Query:
    def __init__(self, a, b):
        self.a = a
        self.b = b

        self.query = self.make_query()


    def make_query(self):
        # create query request using self.a and self.b
        return query


    def make_request_and_get_response(self):  # <--- the 'dangerous' method that can raise exceptions
        with contextlib.closing(urlopen(self.query)) as response:
            return response.read().decode('utf-8')

主文件

from foo.query import *

def get_response_from_external_api(query):
    try:
        response = query.make_request_and_get_response()
    except urllib.error.HTTPError as e:
        print('Got a HTTPError: ', e)
    except Exception:
        print('Got a generic Exception!')
        # handle this exception


if __name__ == "__main__":    
    query = Query('input A', 'input B')
    result = get_response_from_external_api(query)
    return result

使用pytest,我试图模拟那个“危险”的方法 ( make_request_and_get_response),它对特定的异常有副作用。然后,我继续创建一个模拟Query对象以在调用时使用,make_request_and_get_response并期望最后一次调用会给出“URLError”异常。

test_main.py

import pytest
from unittest.mock import patch
from foo.query import Query
from foo.main import get_response_from_external_api


class TestExternalApiCall:
    @patch('foo.query.Query')
    def test_url_error(self, mockedQuery):
        with patch('foo.query.Query.make_request_and_get_response', side_effect=Exception('URLError')):
            with pytest.raises(Exception) as excinfo:
                q= mockedQuery()
                foo.main.get_response_from_external_api(q)
            assert excinfo.value = 'URLError'
            # assert excinfo.value.message == 'URLError' # this gives object has no attribute 'message'

上面的测试给出了以下错误:

>       foo.main.get_response_from_external_api(q)
E       Failed: DID NOT RAISE <class 'Exception'> id='72517784'>") == 'URLError'

即使我捕获'URLError'异常然后在get_response_from_external_api.

有人可以帮助我了解我缺少什么以便能够在 pytest 中引发异常吗?


根据@SimeonVisser 的回复更新:

如果我修改main.py以删除except Excpetion案例:

def get_response_from_external_api(query):
    try:
        response = query.make_request_and_get_response()
    except urllib.error.URLError as e:
        print('Got a URLError: ', e)
    except urllib.error.HTTPError as e:
        print('Got a HTTPError: ', e)

然后测试test_main.py

    def test_url_error2(self):
        mock_query = Mock()
        mock_query.make_request_and_get_response.side_effect = Exception('URLError')
        with pytest.raises(Exception) as excinfo:
            get_response_from_external_api(mock_query)
        assert str(excinfo.value) == 'URLError'

测试通过 OK。

4

1 回答 1

4

问题是它get_response_from_external_api()已经捕获了一个Exception并且不会将它提升到该函数之外的任何地方。因此,当您模拟查询并make_request_and_get_response引发异常时,pytest 不会看到它,因为get_response_from_external_api()它已经捕获了它。

如果您将其更改为以下内容,则 pytest 有机会看到它:

    except Exception:
        print('Got a generic Exception!')
        # handle this exception
        raise

测试用例似乎也没有按预期工作。它可以简化为以下内容(直接创建模拟查询并传递它会更容易一些):

import pytest
from unittest import mock
from foo.main import get_response_from_external_api


class TestExternalApiCall:
    def test_url_error(self):
        mock_query = mock.Mock()
        mock_query.make_request_and_get_response.side_effect = Exception('URLError')
        with pytest.raises(Exception) as excinfo:
            get_response_from_external_api(mock_query)
        assert str(excinfo.value) == 'URLError'

然后测试用例通过(除了用 进行上述更改之外raise)。


对问题 1 的回应:

不同之处在于您正在Query使用装饰器进行模拟,但随后您正在模拟foo.query.Query测试用例中的类以引发该异常。但是在任何时候mockedQuery实际上都不会改变为该方法做任何不同的事情。没有任何特别之处q的常规实例也是如此。mock.Mock()

您可以将其更改为以下内容(类似于上述方法):

import pytest
from unittest.mock import patch
from foo.query import Query
from foo.main import get_response_from_external_api


class TestExternalApiCall:
    @patch('foo.query.Query')
    def test_url_error(self, mockedQuery):
        with patch.object(mockedQuery, 'make_request_and_get_response', side_effect=Exception('URLError')):
            with pytest.raises(Exception) as excinfo:
                get_response_from_external_api(mockedQuery)
            assert str(excinfo.value) == 'URLError'

with patch('foo.query.Query........如果代码中的任何地方随后Query从该位置实例化了一个实例,那么您的语句将起作用。

对问题 2 的回应:

嗯,我无法重现-您在本地拥有的测试用例与问题中的相同吗?我不得不将其修改为foo.main.get_response_from_external_api(q)不存在(foo未导入),因此我将其更改为 callget_response_from_external_api(q)并且不断得到:

>               get_response_from_external_api(q)
E               Failed: DID NOT RAISE <class 'Exception'>
于 2019-11-14T19:30:52.717 回答