简体   繁体   English

pytest:如何在单元测试期间强制提​​高异常?

[英]pytest: How to force raising Exceptions during unit-testing?

In my python code, I am expecting exceptions could possibly be raised after calling method requests.Session.request() , for example these: 在我的python代码中,我期望在调用方法requests.Session.request()之后可能会引发异常,例如:

  • requests.exceptions.ConnectTimeout
  • requests.exceptions.ReadTimeout
  • requests.exceptions.Timeout

When any of these expected exceptions are raised, I handle them appropriately, for example possibly a retry situation. 当引发任何这些预期的异常时,我会适当地处理它们,例如可能是重试情况。

My question, I am using py.test for unit testing, and I purposely want to inject raising exceptions from specific parts of my code. 我的问题是,我使用py.test进行单元测试,我故意想从代码的特定部分注入异常。 For example, the function that calls requests.Session.request() , instead of returning a valid requests.Response , it raises a requests.exception . 例如,调用requests.Session.request()的函数,而不是返回有效的requests.Response ,它会引发requests.exception

What I want to make sure that my code successfully handles expected and unexpected exceptions coming from other packages, which include those exceptions from requests . 我想确保我的代码成功处理来自其他包的预期和意外异常,其中包括来自requests异常。

Maybe... Is there a @decorator that I could add to the aforementioned function to raise exceptions upon request during unit testing? 也许......是否有一个@decorator我可以添加到上述功能中,以便在单元测试期间根据请求引发异常?

Suggestions for doing exceptions injections for unit testing? 为单元测试进行异常注射的建议? (proper phrasing of my question would be greatly appreciated.) (对我的问题的恰当措辞将不胜感激。)

Thanks for the responses!!! 谢谢你的回复!

Here is the entire singleton class that creates requests.Session and calls requests.Session.request() : 这是创建requests.Session并调用requests.Session.request()的整个单例类:

class MyRequest(metaclass=Singleton):

    def __init__(self, retry_tries=3, retry_backoff=0.1, retry_codes=None):
        self.session = requests.session()

        if retry_codes is None:
            retry_codes = set(REQUEST_RETRY_HTTP_STATUS_CODES)

        self.session.mount(
            'http',
            HTTPAdapter(
                max_retries=Retry(
                    total=retry_tries,
                    backoff_factor=retry_backoff,
                    status_forcelist=retry_codes,
                ),
            ),
        )

    def request(self, request_method, request_url, **kwargs):
        try:
            return self.session.request(method=request_method, url=request_url, **kwargs)
        except Exception as ex:
            log.warning(
                "Session Request: Failed: {}".format(get_exception_message(ex)),
                extra={
                    'request_method': request_method,
                    'request_url': request_url
                }
            )
            raise

You can make use of py.test raises, check it here: http://doc.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions 您可以使用py.test加注,请在此处查看: http ://doc.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions

Taking into account your code you could do something along the lines of the following: 考虑到您的代码,您可以执行以下操作:

from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout
from unittest.mock import patch
import pytest

class TestRequestService:
   @patch('path_to_module.MyRequest')
   def test_custom_request(self, my_request_mock):
      my_request_mock.request.side_effect = ConnectTimeout

      with pytest.raises(ConnectTimeout):
          my_request_mock.request(Mock(), Mock())

Moreover, you could make use of pytest.parametrize( http://doc.pytest.org/en/latest/parametrize.html ) as well: 此外,您还可以使用pytest.parametrize( http://doc.pytest.org/en/latest/parametrize.html ):

from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout
from unittest.mock import patch
import pytest

class TestRequestService:
    @pytest.mark.parametrize("expected_exception", [ConnectTimeout, ReadTimeout, Timeout])
    @patch('path_to_module.MyRequest')
    def test_custom_request(self, my_request_mock, expected_exception):
        my_request_mock.request.side_effect = expected_exception
        with pytest.raises(expected_exception):
            my_request_mock.request(Mock(), Mock())

Here you can find some more examples about parametrize: http://layer0.authentise.com/pytest-and-parametrization.html 在这里,您可以找到有关参数化的更多示例: http//layer0.authentise.com/pytest-and-parametrization.html

In my application I am catching exception requests.exceptions.ConnectionError and returning message which is in expected variable below. 在我的应用程序中,我捕获异常requests.exceptions.ConnectionError并返回下面的expected变量中的消息。 So the test looks like this: 所以测试看起来像这样:

import pytest
import requests

expected = {'error': 'cant connect to given url'}

class MockConnectionError:
    def __init__(self, *args, **kwargs):
        raise requests.exceptions.ConnectionError

def test_project_method(monkeypatch):
    monkeypatch.setattr("requests.get", MockConnectionError)
    response = project_method('http://some.url.com/')
    assert response == expected

Patching, mocking and dependecy-injection are techniques to inject fake objects. 修补,嘲弄和依赖注入是注入假物体的技术。 Patching is sometimes hard to do right, on the other hand dependency injection requires that have to change the code you want to test. 修补有时很难正确,另一方面,依赖注入要求必须更改要测试的代码。

This is just a simple example how to use dependency-injection. 这只是一个如何使用依赖注入的简单示例。 First the code we want to test: 首先是我们要测试的代码:

import requests
...  

def fetch_data(url, get=requests.get): 
    return get(url).json()

# this is how we use fetch_data in productive code:
answer = fetch_data("www.google.com?" + term)

And this is then the test: 然后是测试:

import pytest

def test_fetch():

    def get_with_timeout(url):
        raise ConnectTimeout("message")

    with pytest.raises(ConnectTimeout) as e:
         # and now we inject the fake get method:
         fetch_data("https://google.com", get=get_with_timeout)

    assert e.value == "message"

In your example above, the mocking technique would be as follows: 在上面的示例中,模拟技术如下:

def test_exception():

    class TimeoutSessionMock:

        def get(self, *args, **kwargs):
            raise ConnectTimeout("message")

    mr = MyRequest()
    mr.session = TimeoutSessionMock()

    with pytest.raises(ConnectTimeout) as e: 
         mr.request("get", "http://google.com")

    assert e.value == "message"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM