Як за допомогою 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)
         ....

З: Як я можу зробити 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)

може мати сенс перевірити цей шлях, імхо.


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

Відповіді:


129

Тест не вдасться, якщо викликає будь-який несподіваний виняток. Ви можете просто викликати foo (7), і ви перевірите, що MyError не викликається. Отже, буде достатньо наступного:

def test_foo3():
    foo(7)

Якщо ви хочете бути явним і написати твердження для цього, ви можете зробити:

def test_foo3():
    try:
        foo(7)
    except MyError:
        pytest.fail("Unexpected MyError ..")

3
Дякую, це працює, але, схоже, це більше хакерство, ніж чисте рішення. Наприклад, тест для foo (4) не вдасться, але не через помилку твердження.
paraklet

тест для foo (4) не вдасться, оскільки викине виняток, який не очікувався. Іншим способом було б обернути його в блоці try catch і провалити за допомогою конкретного повідомлення. Я оновлю свою відповідь.
Фарук Сахін

1
Якщо у вас багато таких випадків, може бути корисно написати це в простій функції: `` def not_raises (error_class, func, * args, ** kwargs): ... `` Або ви можете написати з подібним підходом, як це робить pytest. Якщо ви це зробите, я пропоную вам написати PR з цим на користь усім. :) (сховище знаходиться в bitbucket ).
Бруно Олівейра

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

Здається, це працює. Однак я не впевнений, чи справді це добре читається в тесті.


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