Как правильно утверждать, что в pytest возникает исключение?

Код:

# coding=utf-8
import pytest


def whatever():
    return 9/0

def test_whatever():
    try:
        whatever()
    except ZeroDivisionError as exc:
        pytest.fail(exc, pytrace=True)

Выход:

================================ test session starts =================================
platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2
plugins: django, cov
collected 1 items 

pytest_test.py F

====================================== FAILURES ======================================
___________________________________ test_whatever ____________________________________

    def test_whatever():
        try:
            whatever()
        except ZeroDivisionError as exc:
>           pytest.fail(exc, pytrace=True)
E           Failed: integer division or modulo by zero

pytest_test.py:12: Failed
============================== 1 failed in 1.16 seconds ==============================

Как сделать трассировку печати pytest, чтобы я мог видеть, где в функции whatever возникло исключение?


person Gill Bates    schedule 28.04.2014    source источник
comment
Я получаю всю трассировку, Ubuntu 14.04, Python 2.7.6   -  person thefourtheye    schedule 28.04.2014
comment
@thefourtheye Пожалуйста, опишите суть вывода. Я пробовал с Python 2.7.4 и Ubunthu 14.04 - с тем же результатом, что я описал в основном сообщении.   -  person Gill Bates    schedule 28.04.2014


Ответы (12)


pytest.raises(Exception) - это то, что вам нужно.

Код

import pytest

def test_passes():
    with pytest.raises(Exception) as e_info:
        x = 1 / 0

def test_passes_without_info():
    with pytest.raises(Exception):
        x = 1 / 0

def test_fails():
    with pytest.raises(Exception) as e_info:
        x = 1 / 1

def test_fails_without_info():
    with pytest.raises(Exception):
        x = 1 / 1

# Don't do this. Assertions are caught as exceptions.
def test_passes_but_should_not():
    try:
        x = 1 / 1
        assert False
    except Exception:
        assert True

# Even if the appropriate exception is caught, it is bad style,
# because the test result is less informative
# than it would be with pytest.raises(e)
# (it just says pass or fail.)

def test_passes_but_bad_style():
    try:
        x = 1 / 0
        assert False
    except ZeroDivisionError:
        assert True

def test_fails_but_bad_style():
    try:
        x = 1 / 1
        assert False
    except ZeroDivisionError:
        assert True

Вывод

============================================================================================= test session starts ==============================================================================================
platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4
collected 7 items 

test.py ..FF..F

=================================================================================================== FAILURES ===================================================================================================
__________________________________________________________________________________________________ test_fails __________________________________________________________________________________________________

    def test_fails():
        with pytest.raises(Exception) as e_info:
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:13: Failed
___________________________________________________________________________________________ test_fails_without_info ____________________________________________________________________________________________

    def test_fails_without_info():
        with pytest.raises(Exception):
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:17: Failed
___________________________________________________________________________________________ test_fails_but_bad_style ___________________________________________________________________________________________

    def test_fails_but_bad_style():
        try:
            x = 1 / 1
>           assert False
E           assert False

test.py:43: AssertionError
====================================================================================== 3 failed, 4 passed in 0.02 seconds ======================================================================================

Обратите внимание, что e_info сохраняет объект исключения, чтобы вы могли извлечь из него подробности. Например, если вы хотите проверить стек вызовов исключения или другое вложенное исключение внутри.

person Murilo Giacometti    schedule 24.04.2015
comment
Было бы хорошо, если бы вы могли включить пример, действительно запрашивающий e_info. Для разработчиков, более знакомых с некоторыми другими языками, не очевидно, что область действия e_info выходит за пределы блока with. - person cjs; 30.05.2018
comment
Это полезно, если вы ожидаете возникновения исключения для вашего теста. Это не очень полезно, если ваш тест может вызвать исключение, и вы хотите обработать его аккуратно. - person rrlamichhane; 28.05.2021

Вы имеете в виду что-то вроде этого:

def test_raises():
    with pytest.raises(Exception) as execinfo:   
        raise Exception('some info')
    # these asserts are identical; you can use either one   
    assert execinfo.value.args[0] == 'some info'
    assert str(execinfo.value) == 'some info'
person simpleranchero    schedule 07.05.2014
comment
excinfo.value.message у меня не сработало, пришлось использовать str(excinfo.value), добавил новый ответ - person d_j; 24.05.2017
comment
@d_j, assert excinfo.value.args[0] == 'some info' - прямой способ доступа к сообщению - person maxschlepzig; 02.07.2017
comment
assert excinfo.match(r"^some info$") тоже работает - person Robin Nemeth; 15.05.2018
comment
Начиная с версии 3.1 вы можете использовать аргумент ключевого слова match, чтобы утверждать, что исключение соответствует тексту или регулярному выражению: with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None") или with raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42") - person Ilya Rusin; 05.10.2018

В pytest есть два способа справиться с подобными случаями:

  • Использование функции pytest.raises

  • Использование pytest.mark.xfail декоратора

Как говорится в документации:

Использование pytest.raises, вероятно, будет лучше для случаев, когда вы тестируете исключения, которые сознательно создает ваш собственный код, тогда как использование @pytest.mark.xfail с функцией проверки, вероятно, лучше для чего-то вроде документирования нефиксированных ошибок (где тест описывает, что «должно» произойти) или ошибок. в зависимостях.

Использование pytest.raises:

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()

Использование pytest.mark.xfail:

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()

Вывод pytest.raises:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever PASSED


======================== 1 passed in 0.01 seconds =============================

Выход pytest.xfail маркера:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever xfail

======================== 1 xfailed in 0.03 seconds=============================
person veri_pudicha_coder    schedule 15.11.2017
comment
xfail не является решением проблемы, он просто позволяет тесту не пройти. Здесь мы хотели бы проверить, возникает ли определенное исключение. - person Ctrl-C; 05.05.2020
comment
Я должен повторить комментарий @ Ctrl-C: pytest.mark.xfail НЕ утверждает, что было возбуждено исключение, он просто позволяет его вызвать. Это не то, о чем спрашивает название вопроса. - person Dean Gurvitz; 27.07.2020
comment
Как есть, этот ответ вводит в заблуждение. Параграф документации, объясняющий xfail, следует переместить в начало. - person Nulano; 20.08.2020
comment
@Nulano Спасибо за предложения. Я обновил ответ! - person veri_pudicha_coder; 04.11.2020

ты можешь попробовать

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 
person d_j    schedule 24.05.2017
comment
Чтобы получить сообщение / значение исключения в виде строки в pytest 5.0.0, необходимо использовать str(excinfo.value). Он также работает в pytest 4.x. В pytest 4.x str(excinfo) также работает, но не работает в pytest 5.0.0. - person Makyen♦; 29.06.2019

pytest постоянно развивается, и с одним из приятных изменений в недавнем прошлом теперь можно одновременно тестировать

  • тип исключения (строгий тест)
  • сообщение об ошибке (строгая или свободная проверка с использованием регулярного выражения)

Два примера из документации:

with pytest.raises(ValueError, match='must be 0 or None'):
    raise ValueError('value must be 0 or None')
with pytest.raises(ValueError, match=r'must be \d+$'):
    raise ValueError('value must be 42')

Я использовал этот подход в ряде проектов, и он мне очень нравится.

person Dr. Jan-Philip Gehrcke    schedule 12.06.2019

Правильный способ - использовать pytest.raises, но я нашел интересный альтернативный способ в комментариях здесь и хочу сохранить это для будущих читателей этого вопроса:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True
person Alexey Shrub    schedule 22.08.2018
comment
Это плохой стиль. Если объект не вызывает ошибку или вызывает непредвиденную ошибку, вы получаете assert False без какого-либо контекста для отчета о тестировании или анализа. Отслеживание pytest останавливается на утверждении, и неясно, что пытались протестировать. Даже в исходном комментарии упоминаются способы получше, но просто чтобы показать, насколько быстро нужно написать тест. Почему это стоило сохранить без этого контекста? - person Zim; 25.08.2020

Это решение то, что мы используем:

def test_date_invalidformat():
    """
    Test if input incorrect data will raises ValueError exception
    """
    date = "06/21/2018 00:00:00"
    with pytest.raises(ValueError):
        app.func(date) #my function to be tested

См. Pytest, https://docs.pytest.org/en/latest/reference.html#pytest-raises

person SMDC    schedule 25.06.2018

Лучшей практикой будет использование класса, наследующего unittest.TestCase и запускающего self.assertRaises.

Например:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

    def test_whatever():
        with self.assertRaises(ZeroDivisionError):
            whatever()

Затем вы должны выполнить его, запустив:

pytest -vs test_path
person kerbelp    schedule 02.01.2017
comment
Лучшая практика, чем что? Я бы не стал называть использование синтаксиса unittest вместо синтаксиса pytest лучшей практикой. - person Jean-François Corbett; 14.11.2017
comment
Возможно, это не «лучше», но это полезная альтернатива. Поскольку критерием ответа является полезность, я голосую за. - person Reb.Cabin; 29.04.2018
comment
похоже, что pytest более популярен, чем nosex, но именно так я использую pytest. - person Gang; 24.08.2018

Есть два способа обрабатывать исключения в pytest:

  1. Использование pytest.raises для написания утверждений о возбужденных исключениях
  2. Использование @pytest.mark.xfail

1. Использование pytest.raises

Из документов:

Чтобы писать утверждения о возбужденных исключениях, вы можете использовать pytest.raises в качестве диспетчера контекста.

Примеры:

Заявление об исключении:

import pytest


def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

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

Если вам нужен доступ к актуальной информации об исключении:

import pytest

def f():
    f()

def test_recursion_depth():
    with pytest.raises(RuntimeError) as excinfo:
        f()
    assert "maximum recursion" in str(excinfo.value)

excinfo - это экземпляр ExceptionInfo, который является оболочкой вокруг фактического возникшего исключения. Основными интересующими атрибутами являются .type, .value и .traceback.

2. Использование @pytest.mark.xfail

Также можно указать raises аргумент для pytest.mark.xfail.

import pytest

@pytest.mark.xfail(raises=IndexError)
def test_f():
    l = [1, 2, 3]
    l[10]

@pytest.mark.xfail(raises=IndexError) говорит, что все, что находится в следующем блоке кода, должно вызывать IndexError исключение. Если поднят IndexError, тест помечается как xfailed (x). Если исключение не возникает, тест помечается как xpassed (X). Если тест вызывает другое исключение, он не выполняется.

Примечания:

  • Использование pytest.raises, вероятно, будет лучше для случаев, когда вы тестируете исключения, которые сознательно создает ваш собственный код, тогда как использование @pytest.mark.xfail с функцией проверки, вероятно, лучше для чего-то вроде документирования нефиксированных ошибок или ошибок в зависимостях.

  • Вы можете передать параметр ключевого слова match диспетчеру контекста (pytest.raises), чтобы проверить соответствие регулярного выражения строковому представлению исключения. (подробнее)

person lmiguelvargasf    schedule 08.07.2020

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

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

@python.mark.parametrize("request_query, response_code", query_response_dataset)
def test_big_query_submission(request_query, response_code):
    try:
        stats = bigquery.Client().query(request_query)
    except Exception as e:
        assert False, f"Exception raised: {e}"
    assert stats.errors is None

Таким образом, вы можете изящно провалить тест вместо того, чтобы по какой-либо причине вывести его из строя из-за возбужденного исключения.

person rrlamichhane    schedule 28.05.2021

Если вы хотите проверить конкретный тип ошибки, используйте комбинацию try, catch и raise:

#-- test for TypeError
try:
  myList.append_number("a")
  assert False
except TypeError: pass
except: assert False
person Elendurwen    schedule 24.09.2020

Вы пытались удалить "pytrace = True"?

pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after

Вы пробовали работать с --fulltrace?

person Loic Pantou    schedule 11.02.2016