Как создать расширенный маркер xfail для py.test? - PullRequest
1 голос
/ 01 июля 2019

В py.test (версия 4.6.2) у вас есть разметчик для теста, чтобы пометить его как неудачный, например,

@pytest.mark.xfail
def test1():
    return 1/0

Также можно проверить само исключение

@pytest.mark.xfail(raises=ZeroDivisionError)

но можно ли каким-то образом проверить само сообщение об ошибке?

это полезно, когда у вас есть HTTPError, так как может быть много причин.А когда вы сравниваете само сообщение об ошибке, вы можете гораздо более точно определить, когда тест не пройден (например, отличить определенный Client Error от Server Error).

Пока что я использую следующую конструкцию:

def test_fail_request(self):      
    with pytest.raises(requests.exceptions.HTTPError) as excinfo:
        response = requests.something
    assert '403 Client Error: Not Found' in str(excinfo.value)

, но, конечно, тест, подобный следующему, будет более читабельным, компактным и правильно обработанным py.test:

 @pytest.mark.xfail(expected_error = "403 Client Error: Not Found"):
 def test_fail_request(self):      
    response = requests.something

Есть ли способ реализовать это поведение / функцию?

Для пояснения ожидается, что последний пример кода завершится неудачно, но только когда сообщение об ошибке содержит определенное сообщение (пример: 400 Client Error: Bad Request).В этом случае об испытании будет сообщено как XFAIL.

Но если тест не пройден и создается сообщение об ошибке другое (даже для того же исключения, но вместо этого, например, 500 Server Error в сообщении об ошибке), тест должен быть указан как «неожиданно»мимоходом »(XPASS).

Ответы [ 2 ]

2 голосов
/ 01 июля 2019

Если сбой является нормальным поведением, вы можете создать собственный декоратор, например ::1001

import functools
def expect_http_error(func=None, *, expected_error):
  def wrapper(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
      with pytest.raises(requests.exceptions.HTTPError) as excinfo:
        func(*args, **kwargs)
      assert expected_error in str(excinfo.value)

    return inner

  return wrapper if func is None else wrapper(func)

И тогда вы используете это так:

@expect_http_error(expected_error = "403 Client Error: Not Found")
def test_fail_request(self):      
  response = requests.something
0 голосов
/ 01 июля 2019

Хранение пользовательских данных в маркерах * Маркеры 1001 * pytest принимают произвольные аргументы, поэтому довольно просто расширить встроенные маркеры с помощью пользовательских аргументов: @pytest.mark.xfail(raises=ZeroDivisionError, match='.* by zero') def test(): return 1 / 0 Хотя аргумент matchне определен для маркера xfail, он, тем не менее, будет сохранен, но по умолчанию останется неиспользованным.Затем вы можете получить доступ к аргументу в настраиваемых подсобных крючках. Использование настраиваемых данных маркера в перехватах

Предупреждение :

Но если тест не пройдени создает другое сообщение об ошибке (даже для того же исключения, но вместо этого, например, 500 Server Error в сообщении об ошибке), тест должен быть объявлен как «неожиданно пройденный» (XPASS).

Обратите внимание, что pytest не пройдёт тест, если возникнет исключение непредвиденного типа, например:

@pytest.mark.xfail(raises=RuntimeError)
def test():
    return 1 / 0

будет сообщено как неудачный:

============================================== FAILURES ==============================================
_______________________________________________ test ________________________________________________

    @pytest.mark.xfail(raises=RuntimeError)
    def test():
>       return 1 / 0
E       ZeroDivisionError: division by zero

test_spam.py:18: ZeroDivisionError

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

  • , если тест имеет «стандартную» отметку xfail (без аргумента match), например,

    @pytest.mark.xfail(raises=Foo)
    

    тест будет неуспешным, если Foo поднят, и завершится неудачей в противном случае (pytest стандартное поведение)

  • , если тест имеет отметку xfail с matchаргумент, например,

    @pytest.mark.xfail(raises=Foo, match='.*bar.*')
    

    будет только xfail, если будет повышено Foo с сообщением, соответствующим .*bar.* регулярному выражению, во всех других случаях (например, повышено Foo с другим сообщением, поднял Bar и т. д.) будет xpass.

hookimpl:

# conftest.py

import re
from pytest import hookimpl


@hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()
    if not hasattr(item, '_evalxfail'):
        return  # no xfail marker

    evalxfail = item._evalxfail
    match_expr = evalxfail.get('match')
    if match_expr is None:
        return  # no match argument in xfail marker

    if call.excinfo and evalxfail.wasvalid() and evalxfail.istrue():
        match = re.search(match_expr, str(call.excinfo.value))
        # if the exception doesn't match the type or description, set to XPASS 
        if match or evalxfail.invalidraise(call.excinfo.value):
            rep.outcome = 'passed'
            rep.wasxfail = evalxfail.getexplanation()

Для дальнейшей настройки измените проверку состояния в hook.Например, если вы хотите:

  • xfail, если исключение имеет правильный тип и соответствует регулярному выражению сообщения
  • , ошибка, если исключение имеет неправильный тип
  • xpass, если исключение имеет правильный тип, но не соответствует регулярному выражению

, измените строку

if match or evalxfail.invalidraise(call.excinfo.value):

на

if match:

и т.д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...