Як правильно стверджувати, що виняток збільшується в pytest?


293

Код:

# 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функції піднято виняток?


Я отримую весь трекбек, Ubuntu 14.04, Python 2.7.6
thefourtheye

@thefourtheye Будь ласка, зробіть суть із результатом. Я намагався з Python 2.7.4 та Ubunthu 14.04 - з тим самим результатом, як я описав у головному дописі.
Gill Bates

1
@GillBates Ви можете позначити правильну відповідь ??
Гонсало Гарсія

Відповіді:


332

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зберігається об’єкт виключення, щоб ви могли витягти з нього деталі. Наприклад, якщо ви хочете перевірити стек викликів винятків або інший вкладений виняток всередині.


35
Було б добре, якби ви могли включити приклад, який насправді запитує e_info. Для розробників, більш знайомих з певними іншими мовами, не очевидно, що сфера дії e_infoпоширюється поза withблоком.
cjs

153

Ви маєте на увазі щось подібне:

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'

16
excinfo.value.messageне працювало для мене, довелося скористатися str(excinfo.value), додав нову відповідь
d_j

5
@d_j, assert excinfo.value.args[0] == 'some info'це прямий спосіб отримати доступ до повідомлення
maxschlepzig

assert excinfo.match(r"^some info$")працює також
Робін Немет

14
Із версії 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")
Ілля Русін

58

Є два способи вирішення подібних випадків у pytest:

  • Використання 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=============================

Як зазначено в документації :

Використання pytest.raises, ймовірно, буде кращим у випадках, коли ви перевіряєте винятки, власний код навмисно підвищується, тоді як використання @pytest.mark.xfailфункції перевірки, ймовірно, краще для чогось, наприклад, документування нефіксованих помилок (де тест описує, що "повинно" статися) або помилок у залежності .


xfailтут не є вирішенням проблеми, вона просто дозволяє випробувати тест. Тут ми хотіли б перевірити, чи порушено певний виняток.
Ctrl-C

44

Ви можете спробувати

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 

Щоб отримати повідомлення / значення виключення у вигляді рядка в pytest 5.0.0, str(excinfo.value)потрібно використовувати. Він також працює в pytest 4.x. У pytest 4.x str(excinfo)також працює, але не працює в pytest 5.0.0.
Макіен

Це те, що я шукав. Дякую
Eystein Bye

15

Пісттест постійно розвивається, і з однією з приємних змін у недавньому минулому тепер можна одночасно перевірити

  • тип винятку (суворий тест)
  • повідомлення про помилку (сувора чи невірна перевірка за допомогою регулярного виразу)

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

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')

Я використовую такий підхід у ряді проектів і мені це дуже подобається.


6

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

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True

4

Це рішення - це те, що ми використовуємо:

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


2

Кращою практикою буде використання класу, який успадковує 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

4
Краща практика, ніж що? Я б не закликав використовувати синтаксис unittest замість синтаксису pytest "кращою практикою".
Жан-Франсуа Корбетт

2
Це може бути не «краще», але це корисна альтернатива. Оскільки критерієм відповіді є корисність, я підтримую заяву.
Reb.Cabin

схоже, pytestце більш популярно nosex, але саме так я використовую pytest.
Банда

0

Ви намагалися видалити "pytrace = True"?

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

Ви спробували запустити з '--fulltrace'?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.