Как использовать `pytest` для проверки исключений?

Для проверки исключений в `pytest` можно использовать контекстный менеджер `pytest.raises`:

import pytest

def test_exception():
    with pytest.raises(ValueError):
        raise ValueError("Неверное значение")

def test_specific_exception_message():
    with pytest.raises(ValueError, match=".*Неверное.*"):
        raise ValueError("Неверное значение")
  
`pytest.raises` принимает тип исключения и, опционально, строку для сравнения с сообщением исключения через параметр `match`. Убедитесь, что код, который должен вызвать исключение, находится внутри блока `with`.

Для проверки исключений в pytest существует несколько основных подходов. Наиболее распространенные и рекомендуемые:

  1. pytest.raises как контекстный менеджер:

    Это наиболее идиоматичный и читаемый способ. Используйте pytest.raises как контекстный менеджер (with pytest.raises(...)). Код, который должен вызывать исключение, помещается внутрь блока with. pytest автоматически проверит, что было выброшено ожидаемое исключение. Если исключение не выбрасывается или выбрасывается исключение другого типа, тест завершится неудачей.

    import pytest
    
    def my_function(x):
        if x < 0:
            raise ValueError("x must be non-negative")
        return x * 2
    
    def test_my_function_raises_valueerror():
        with pytest.raises(ValueError):
            my_function(-1)
    
    def test_my_function_works_correctly():
      assert my_function(2) == 4
    

    В этом примере проверяется, что my_function(-1) выбрасывает исключение ValueError.

    Вы можете получить доступ к объекту исключения:

    def test_my_function_raises_valueerror_with_message():
        with pytest.raises(ValueError) as excinfo:
            my_function(-1)
        assert "x must be non-negative" in str(excinfo.value)
    

    excinfo содержит информацию об исключении, например, его тип, значение и traceback.

  2. pytest.raises как функция-обертка:

    Реже используемый способ, но все еще валидный. pytest.raises может использоваться как функция-обертка для вызова функции, которая, как ожидается, выбросит исключение. Этот способ полезен, когда необходимо, например, в цикле протестировать вызовы функций с различными аргументами, которые должны вызывать исключения.

    def test_my_function_raises_valueerror_functional():
        with pytest.raises(ValueError):
            pytest.raises(ValueError, my_function, -1) # Устаревший способ
        # более предпочтительный способ (аналогично контекстному менеджеру):
        with pytest.raises(ValueError):
          my_function(-1)
    

    Важно отметить, что в последнем примере использование pytest.raises(ValueError, my_function, -1) немного устарело и рекомендуется использовать контекстный менеджер для большей читаемости и ясности.

  3. match параметр для проверки сообщения исключения (Python 3.10+):

    Начиная с Python 3.10, в pytest.raises добавлен параметр match, позволяющий проверять, соответствует ли сообщение исключения заданному регулярному выражению. Это упрощает проверку конкретных деталей исключения.

    import pytest
    
    def test_my_function_raises_valueerror_with_message_regex():
        with pytest.raises(ValueError, match="x must be non-negative"):
            my_function(-1)
    
    def test_my_function_raises_valueerror_with_message_regex_partial():
        with pytest.raises(ValueError, match="non-negative"): # проверяется частичное совпадение
            my_function(-1)
    
  4. try...except (Не рекомендуется):

    Хотя можно использовать блоки try...except для перехвата исключений, это не рекомендуется для написания тестов в pytest. Этот подход менее явный и может приводить к пропуску ошибок, если исключение выбрасывается в неожиданном месте или если тип исключения не совпадает. Использование pytest.raises делает намерение более ясным и обеспечивает лучшую обработку ошибок.

    # Не рекомендуется!
    def test_my_function_raises_valueerror_bad():
        try:
            my_function(-1)
        except ValueError:
            pass
        else:
            assert False, "ValueError was not raised"
    

    В целом, использование pytest.raises как контекстного менеджера является предпочтительным способом проверки исключений в pytest. Он обеспечивает четкость, читаемость и надежность при написании тестов.

0