Как использовать pytest, чтобы убедиться, что ошибка НЕ ​​возникает

86

Предположим, у нас есть что-то вроде этого:

import py, pytest

ERROR1 = ' --- Error : value < 5! ---'
ERROR2 = ' --- Error : value > 10! ---'

class MyError(Exception):
    def __init__(self, m):
        self.m = m

    def __str__(self):
        return self.m

def foo(i):
    if i < 5:
        raise MyError(ERROR1)
    elif i > 10:
        raise MyError(ERROR2)
    return i


# ---------------------- TESTS -------------------------
def test_foo1():
    with pytest.raises(MyError) as e:
        foo(3)
    assert ERROR1 in str(e)

def test_foo2():
    with pytest.raises(MyError) as e:
        foo(11)
    assert ERROR2 in str(e)

def test_foo3():
        ....
        foo(7)
         ....

Q: Как я могу заставить test_foo3 () проверять, что MyError не возникает? Очевидно, что я мог просто протестировать:

def test_foo3():
    assert foo(7) == 7

но я хочу проверить это через pytest.raises (). Возможно ли как-нибудь? Например: в случае, когда функция "foo" вообще не имеет возвращаемого значения,

def foo(i):
    if i < 5:
        raise MyError(ERROR1)
    elif i > 10:
        raise MyError(ERROR2)

имеет смысл протестировать таким образом, imho.

параклет
источник
Похоже на поиск проблемы, с тестированием кода foo(7)все в порядке. Вы получите правильное сообщение, и вам будет легче отлаживать весь вывод pytest. Предложение, которое вы выдвинули из @Faruk ( 'Unexpected error...'), ничего не говорит об ошибке, и вы застрянете. Единственное, что вы можете сделать, чтобы улучшить его, - это заявить о своем намерении test_foo3_works_on_integers_within_range().
dhill

Ответы:

129

Тест завершится неудачно, если возникнет непредвиденное исключение. Вы можете просто вызвать foo (7), и вы убедитесь, что MyError не возникает. Итак, достаточно будет следующего:

def test_foo3():
    foo(7)

Если вы хотите быть явным и написать для этого утверждение assert, вы можете сделать:

def test_foo3():
    try:
        foo(7)
    except MyError:
        pytest.fail("Unexpected MyError ..")
Фарук Сахин
источник
3
Спасибо, работает, но, похоже, это скорее взлом, чем чистое решение. Например, проверка для foo (4) завершится неудачно, но не из-за ошибки утверждения.
параклет
проверка для foo (4) завершится ошибкой, поскольку вызовет исключение, которого не ожидалось. Другой способ - заключить его в блок try catch и выдать конкретное сообщение. Я обновлю свой ответ.
Фарук Сахин
1
Если у вас много таких случаев, может быть полезно написать это в простой функции: `` def not_raises (error_class, func, * args, ** kwargs): ... `` Или вы можете написать подобный подходу, как это делает pytest. Если вы это сделаете, я предлагаю вам написать пиар с этим, чтобы принести пользу всем. :) (Репозиторий в битбакете ).
Бруно Оливейра
6
@paraklet - основной слоган pytest - «тестирование без шаблонов» . В духе pytest есть возможность писать тесты, как в первом примере Фарука, в то время как pytest обрабатывает детали за вас. Мне первый пример - это «чистое решение», а второй кажется излишне многословным.
Ник Чаммас,
22

Основываясь на том, что упомянул Ойсин.

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

from contextlib import contextmanager

@contextmanager
def not_raises(exception):
  try:
    yield
  except exception:
    raise pytest.fail("DID RAISE {0}".format(exception))

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

Питикос
источник
1
Я бы хотел, чтобы это было встроено в py.test; в некоторых случаях это сделало бы тесты более удобочитаемыми. Особенно в сочетании с @pytest.mark.parametrize.
Арел
Я высоко ценю чувство читабельности кода при таком подходе!
GrazingScientist
7

Мне было любопытно посмотреть, сработает ли not_raises. Быстрый тест (test_notraises.py):

from contextlib import contextmanager

@contextmanager
def not_raises(ExpectedException):
    try:
        yield

    except ExpectedException, err:
        raise AssertionError(
            "Did raise exception {0} when it should not!".format(
                repr(ExpectedException)
            )
        )

    except Exception, err:
        raise AssertionError(
            "An unexpected exception {0} raised.".format(repr(err))
        )

def good_func():
    print "hello"


def bad_func():
    raise ValueError("BOOM!")


def ugly_func():
    raise IndexError("UNEXPECTED BOOM!")


def test_ok():
    with not_raises(ValueError):
        good_func()


def test_bad():
    with not_raises(ValueError):
        bad_func()


def test_ugly():
    with not_raises(ValueError):
        ugly_func()

Кажется, это работает. Однако я не уверен, действительно ли он хорошо читается в тесте.

Oisin
источник
Обновлено для python3 gist.github.com/oisinmulvihill/45c14271fad7794a4a52516ecb784e69
Oisin