Можно ли изменить поведение оператора assert в Python?

18

Я использую утверждения Python assert, чтобы соответствовать фактическому и ожидаемому поведению. У меня нет контроля над этим, как будто есть ошибки прерванные тестовые случаи. Я хочу взять на себя управление ошибкой утверждения и определить, хочу ли я прервать тестовый сценарий при ошибке подтверждения или нет.

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

Я понятия не имею, как это сделать

Пример кода, мы используем Pytest здесь

import pytest
def test_abc():
    a = 10
    assert a == 10, "some error message"

Below is my expectation

Когда assert выбрасывает assertionError, у меня должна быть возможность приостановить тестирование, и я могу отлаживать и позже возобновлять. Для паузы и возобновления я буду использовать tkinterмодуль. Я сделаю функцию assert, как показано ниже

import tkinter
import tkinter.messagebox

top = tkinter.Tk()

def _assertCustom(assert_statement, pause_on_fail = 0):
    #assert_statement will be something like: assert a == 10, "Some error"
    #pause_on_fail will be derived from global file where I can change it on runtime
    if pause_on_fail == 1:
        try:
            eval(assert_statement)
        except AssertionError as e:
            tkinter.messagebox.showinfo(e)
            eval (assert_statement)
            #Above is to raise the assertion error again to fail the testcase
    else:
        eval (assert_statement)

В дальнейшем я должен изменить каждое утверждение assert с помощью этой функции как

import pytest
def test_abc():
    a = 10
    # Suppose some code and below is the assert statement 
    _assertCustom("assert a == 10, 'error message'")

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

Summary:Мне нужно что-то, где я могу приостановить тестовый случай при сбое, а затем возобновить после отладки. Я знаю об этом, tkinterи именно поэтому я использовал это. Любые другие идеи будут приветствоваться

Note: Вышеуказанный код еще не проверен. Там также могут быть небольшие синтаксические ошибки

Изменить: Спасибо за ответы. Расширение этого вопроса немного впереди сейчас. Что делать, если я хочу изменить поведение assert. В настоящее время, когда есть утверждение об ошибке, тестовый случай завершается. Что делать, если я хочу выбрать, нужен ли мне выход из тестового набора при конкретном сбое подтверждения или нет. Я не хочу писать собственную функцию assert, как упомянуто выше, потому что таким образом я должен измениться в количестве мест

Nitesh
источник
3
Не могли бы вы дать нам пример кода того, что вы хотели бы сделать?
mrblewog
1
Не используйте, assertно пишите свои собственные функции проверки, которые делают то, что вы хотите.
molbdnilo
Почему вы не вставляете assert в блок try и сообщение об ошибке, кроме ?
Пратик Кини
1
Похоже, что вы действительно хотите, чтобы использовать pytestдля ваших тестовых случаев. Он поддерживает использование assert и пропуск тестов вместе со многими другими функциями, которые облегчают написание тестовых наборов.
blubberdiblub
1
Разве не было бы довольно просто написать простой инструмент, который бы механически заменял каждый assert cond, "msg"в вашем коде _assertCustom("assert cond, 'msg'")? Вероятно, sedодин вкладыш мог бы сделать это.
NPE

Ответы:

23

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

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

Обрабатывать исключения, а не утверждать

Обратите внимание, что провальный тест обычно не останавливает pytest; только если вы включили явное указание ему выйти после определенного количества сбоев . Кроме того, тесты не проходят, потому что возникает исключение; assertподнимает, AssertionErrorно это не единственное исключение, которое приведет к провалу теста! Вы хотите контролировать обработку исключений, а не изменять их assert.

Тем не менее, в случае неудачного утверждения будет завершен отдельный тест. Это потому, что как только исключение возникает за пределами try...exceptблока, Python разматывает текущий фрейм функции, и возвращаться к нему уже нельзя.

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

Посмертная отладка в pytest с помощью pdb

Для различных вариантов обработки сбоев в отладчике я начну с --pdbпереключателя командной строки , который открывает стандартное приглашение отладки при сбое теста (выходные данные исключены для краткости):

$ mkdir demo
$ touch demo/__init__.py
$ cat << EOF > demo/test_foo.py
> def test_ham():
>     assert 42 == 17
> def test_spam():
>     int("Vikings")
> EOF
$ pytest demo/test_foo.py --pdb
[ ... ]
test_foo.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(2)test_ham()
-> assert 42 == 17
(Pdb) q
Exit: Quitting debugger
[ ... ]

С помощью этого переключателя, когда тест не пройден, pytest начинает сеанс отладки после смерти . По сути, это именно то, что вы хотели; чтобы остановить код в точке неудачного теста и открыть отладчик, чтобы посмотреть на состояние вашего теста. Вы можете взаимодействовать с локальными переменными теста, глобальными переменными, а также локальными и глобальными переменными каждого кадра в стеке.

Здесь pytest дает вам полный контроль над тем, выходить или нет после этой точки: если вы используете команду qquit, то pytest также завершает выполнение, использование cдля продолжения вернет управление в pytest и будет выполнен следующий тест.

Использование альтернативного отладчика

Вы не связаны с pdbотладчиком для этого; Вы можете установить другой отладчик с помощью --pdbclsпереключателя. Будет работать любая pdb.Pdb()совместимая реализация, включая реализацию отладчика IPython или большинство других отладчиков Python ( отладчик pudb требует использования -sпереключателя или специального плагина ). Коммутатор принимает модуль и класс, например, для использования pudbвы можете использовать:

$ pytest -s --pdb --pdbcls=pudb.debugger:Debugger

Вы можете использовать эту функцию , чтобы написать свой собственный класс - обертку вокруг , Pdbкоторая просто возвращает немедленно , если отказ конкретных не то , что вы заинтересованы в том , pytestиспользует Pdb()так же , как pdb.post_mortem()делает :

p = Pdb()
p.reset()
p.interaction(None, t)

Здесь tнаходится объект трассировки . Когда p.interaction(None, t)возвращается, pytestпродолжается со следующим тестом, если p.quitting не установлено значение True(после чего pytest затем завершается).

Вот пример реализации, которая распечатывает, что мы отказываемся отлаживать, и возвращает немедленно, если только тест не был запущен ValueErrorи сохранен как demo/custom_pdb.py:

import pdb, sys

class CustomPdb(pdb.Pdb):
    def interaction(self, frame, traceback):
        if sys.last_type is not None and not issubclass(sys.last_type, ValueError):
            print("Sorry, not interested in this failure")
            return
        return super().interaction(frame, traceback)

Когда я использую это с вышеприведенной демонстрацией, это вывод (опять же, для краткости):

$ pytest test_foo.py -s --pdb --pdbcls=demo.custom_pdb:CustomPdb
[ ... ]
    def test_ham():
>       assert 42 == 17
E       assert 42 == 17

test_foo.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sorry, not interested in this failure
F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../test_foo.py(4)test_spam()
-> int("Vikings")
(Pdb)

Вышеупомянутые интроспективы sys.last_typeопределяют, является ли неудача «интересной».

Однако я не могу порекомендовать эту опцию, если вы не хотите написать свой собственный отладчик, используя tkInter или что-то подобное. Обратите внимание, что это большое начинание.

Фильтрация сбоев; выбрать, когда открыть отладчик

Следующий уровень является pytest отладки и взаимодействия крюки ; это точки подключения для настройки поведения, чтобы заменить или улучшить то, как pytest обычно обрабатывает такие вещи, как обработка исключения или вход в отладчик через pdb.set_trace()или breakpoint()(Python 3.7 или новее).

Внутренняя реализация этого хука также отвечает за печать >>> entering PDB >>>вышеупомянутого баннера, поэтому использование этого хука для предотвращения отладчика означает, что вы вообще не увидите этот вывод. Вы можете иметь свой собственный хук, а затем делегировать исходный хук, если неудачный тест «интересен», и таким образом фильтровать неудачи теста независимо от того, какой отладчик вы используете! Вы можете получить доступ к внутренней реализации, обратившись к ней по имени ; Внутренний подключаемый модуль для этого называется pdbinvoke. Чтобы предотвратить его запуск, необходимо отменить его регистрацию, но сохранить ссылку. Мы можем вызывать ее напрямую по мере необходимости.

Вот пример реализации такого хука; Вы можете поместить это в любое место, из которого загружаются плагины ; Я вставил это в demo/conftest.py:

import pytest

@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    # unregister returns the unregistered plugin
    pdbinvoke = config.pluginmanager.unregister(name="pdbinvoke")
    if pdbinvoke is None:
        # no --pdb switch used, no debugging requested
        return
    # get the terminalreporter too, to write to the console
    tr = config.pluginmanager.getplugin("terminalreporter")
    # create or own plugin
    plugin = ExceptionFilter(pdbinvoke, tr)

    # register our plugin, pytest will then start calling our plugin hooks
    config.pluginmanager.register(plugin, "exception_filter")

class ExceptionFilter:
    def __init__(self, pdbinvoke, terminalreporter):
        # provide the same functionality as pdbinvoke
        self.pytest_internalerror = pdbinvoke.pytest_internalerror
        self.orig_exception_interact = pdbinvoke.pytest_exception_interact
        self.tr = terminalreporter

    def pytest_exception_interact(self, node, call, report):
        if not call.excinfo. errisinstance(ValueError):
            self.tr.write_line("Sorry, not interested!")
            return
        return self.orig_exception_interact(node, call, report)

Данный плагин использует внутренний TerminalReporterплагин для записи строк в терминал; это делает вывод более чистым при использовании формата статуса компактного теста по умолчанию и позволяет записывать данные в терминал даже с включенным захватом вывода.

В этом примере объект плагина регистрируется с pytest_exception_interactпомощью ловушки через другую ловушку, pytest_configure()но необходимо убедиться, что он работает достаточно поздно (используя @pytest.hookimpl(trylast=True)), чтобы иметь возможность отменить регистрацию внутреннего pdbinvokeплагина. Когда вызывается ловушка, пример проверяет call.exceptinfoобъект ; Вы также можете проверить узел или отчет тоже.

С учетом указанных выше примере кода на месте в demo/conftest.py, то test_hamсбой теста игнорируется, только test_spamошибки теста, который поднимает ValueError, приводит к быстрой отладки открытия:

$ pytest demo/test_foo.py --pdb
[ ... ]
demo/test_foo.py F
Sorry, not interested!

demo/test_foo.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

demo/test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(4)test_spam()
-> int("Vikings")
(Pdb) 

Чтобы повторить, вышеприведенный подход имеет дополнительное преимущество, заключающееся в том, что вы можете комбинировать его с любым отладчиком, который работает с pytest , включая pudb или отладчик IPython:

$ pytest demo/test_foo.py --pdb --pdbcls=IPython.core.debugger:Pdb
[ ... ]
demo/test_foo.py F
Sorry, not interested!

demo/test_foo.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

demo/test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(4)test_spam()
      1 def test_ham():
      2     assert 42 == 17
      3 def test_spam():
----> 4     int("Vikings")

ipdb>

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

Обратите внимание, что определенные плагины отладчика pytest (такие как pytest-pudbили pytest-pycharm) регистрируют свои собственные pytest_exception_interactловушки. Более полная реализация должна была бы перебрать все плагины в менеджере плагинов, чтобы автоматически переопределять произвольные плагины, используя config.pluginmanager.list_name_pluginи hasattr()для тестирования каждого плагина.

Делать неудачи уходят совсем

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

Когда pytest запускает тесты, он запускает тест через описанный выше хук, который, как ожидается, вернет Noneили вызовет исключение. Из этого создается отчет, при желании создается запись в журнале, и если проверка не пройдена, pytest_exception_interact()вызывается вышеупомянутая ловушка. Так что все, что вам нужно сделать, это изменить результат, полученный этим хуком; вместо исключения он просто не должен ничего возвращать.

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

outcome = yield

в вашей реализации обработчика ловушек, и вы получите доступ к результату ловушки , включая исключение теста через outcome.excinfo. Этот атрибут имеет значение кортеж (тип, экземпляр, трассировка), если в тесте возникло исключение. В качестве альтернативы, вы можете позвонитьoutcome.get_result() и использовать стандартную try...exceptобработку.

Так как же пройти тестовый проход? У вас есть 3 основных варианта:

  • Вы можете пометить тест как ожидаемый сбой, позвонивpytest.xfail() оболочку.
  • Вы можете пометить элемент как пропущенный , что означает, что тест никогда не запускался, вызвавpytest.skip() .
  • Вы можете удалить исключение, используя outcome.force_result()метод ; установить результат в пустой список здесь (то есть: зарегистрированный хук не дал ничего, кромеNone ), и исключение будет полностью очищено.

То, что вы используете, зависит от вас. Не забудьте сначала проверить результаты пропущенных тестов и тестов с ожидаемым отказом, поскольку вам не нужно обрабатывать эти случаи, как если бы тест не прошел. Вы можете получить доступ к специальным исключениям, которые эти опции вызывают черезpytest.skip.Exception иpytest.xfail.Exception .

Вот пример реализации, которая помечает неудачные тесты, которые не вызывают ValueError, как пропущенные :

import pytest

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
    outcome = yield
    try:
        outcome.get_result()
    except (pytest.xfail.Exception, pytest.skip.Exception, pytest.exit.Exception):
        raise  # already xfailed,  skipped or explicit exit
    except ValueError:
        raise  # not ignoring
    except (pytest.fail.Exception, Exception):
        # turn everything else into a skip
        pytest.skip("[NOTRUN] ignoring everything but ValueError")

Когда положить в conftest.pyвыходной становится:

$ pytest -r a demo/test_foo.py
============================= test session starts =============================
platform darwin -- Python 3.8.0, pytest-3.10.0, py-1.7.0, pluggy-0.8.0
rootdir: ..., inifile:
collected 2 items

demo/test_foo.py sF                                                      [100%]

=================================== FAILURES ===================================
__________________________________ test_spam ___________________________________

    def test_spam():
>       int("Vikings")
E       ValueError: invalid literal for int() with base 10: 'Vikings'

demo/test_foo.py:4: ValueError
=========================== short test summary info ============================
FAIL demo/test_foo.py::test_spam
SKIP [1] .../demo/conftest.py:12: [NOTRUN] ignoring everything but ValueError
===================== 1 failed, 1 skipped in 0.07 seconds ======================

Я использовал -r a флаг, чтобы прояснить, что test_hamбыло пропущено сейчас.

Если вы замените pytest.skip()вызов на pytest.xfail("[XFAIL] ignoring everything but ValueError"), тест помечается как ожидаемый сбой:

[ ... ]
XFAIL demo/test_foo.py::test_ham
  reason: [XFAIL] ignoring everything but ValueError
[ ... ]

и используя outcome.force_result([])пометки как пройденные:

$ pytest -v demo/test_foo.py  # verbose to see individual PASSED entries
[ ... ]
demo/test_foo.py::test_ham PASSED                                        [ 50%]

Вам решать, какой из них вы считаете наиболее подходящим для вашего варианта использования. Для skip()и xfail()я имитировал стандартный формат сообщения (с префиксом [NOTRUN]или[XFAIL] ), но вы можете использовать любой другой формат сообщения, который вы хотите.

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

Изменение отдельных утверждений

Если вы хотите изменить assertтесты внутри теста , то вы настраиваете себя на гораздо большую работу. Да, это технически возможно, но только путем переписывания самого кода, который Python собирается выполнить во время компиляции .

Когда вы используете pytest, это на самом деле уже делается . Pytest переписывает assertутверждения, чтобы дать вам больше контекста, когда ваши утверждения не выполняются ; см. этот блог для хорошего обзора того, что именно делается, а также _pytest/assertion/rewrite.pyисходного кода . Обратите внимание, что этот модуль имеет длину более 1 тыс. Строк и требует понимания того, как работают абстрактные синтаксические деревья Python . Если вы делаете, вы могли бы monkeypatch этот модуль , чтобы добавить свои собственные модификации там, включая окружающие assertсtry...except AssertionError: обработчиком.

Однако вы не можете просто выборочно отключать или игнорировать утверждения, потому что последующие операторы могут легко зависеть от состояния (конкретные расположения объектов, набор переменных и т. Д.), От которого пропущенное утверждение предназначалось для защиты. Если проверка assert fooне выполняется None, то более позднее подтверждение полагается на foo.barсуществование, тогда вы просто столкнетесь сAttributeError там и т. Д. Придерживайтесь повторного вызова исключения, если вам нужно пойти по этому пути.

Я не буду вдаваться в подробности переписывания assertsздесь, так как не думаю, что это стоит того, чтобы продолжить, не учитывая объем работы и посмертную отладку, которая дает вам доступ к состоянию теста на в любом случае точка подтверждения отказа .

Обратите внимание, что если вы действительно хотите это сделать, вам не нужно использовать eval()(это не сработает в любом случае, assertэто утверждение, поэтому вам нужно будет использовать exec()вместо этого), и при этом вам не придется запускать утверждение дважды (что может привести к проблемам, если выражение, используемое в утверждении изменено состояние). Вместо этого вы бы встроили ast.Assertузел вast.Try узел и подключить обработчик исключений, который использует пустой ast.Raiseузел, повторно вызвать исключение, которое было перехвачено.

Использование отладчика для пропуска утверждений утверждения.

Отладчик Python фактически позволяет пропускать операторы , используя j/ jumpкоманду . Если вы знаете , фронт , что конкретное утверждение будет терпеть неудачу, вы можете использовать это , чтобы обойти его. Вы можете запустить свои тесты с помощью --trace, который открывает отладчик в начале каждого теста , а затем выполнить j <line after assert>команду a, чтобы пропустить его, когда отладчик приостановлен непосредственно перед утверждением.

Вы даже можете автоматизировать это. Используя описанные выше приемы, вы можете создать собственный плагин отладчика, который

  • использует pytest_testrun_call()ловушку, чтобы поймать AssertionErrorисключение
  • извлекает номер строки «ошибочной» строки из трассировки, и, возможно, при некотором анализе исходного кода определяет номера строк до и после утверждения, необходимого для выполнения успешного перехода
  • снова запускает тест , но на этот раз с использованием Pdbподкласса, который устанавливает точку останова на строке перед подтверждением и автоматически выполняет переход к секунде при достижении точки останова с последующим cпродолжением.

Или вместо того, чтобы ждать, пока утверждение не сработает, вы можете автоматизировать установку точек останова для каждого assertнайденного в тесте (опять же, используя анализ исходного кода, вы можете тривиально извлечь номера строк для ast.Assertузлов в AST теста), выполнить проверенный тест используя команды сценария отладчика, и используйте jumpкоманду, чтобы пропустить само утверждение. Вы должны были бы сделать компромисс; запускать все тесты в отладчике (что медленно, поскольку интерпретатору приходится вызывать функцию трассировки для каждого оператора) или применять его только к ошибочным тестам и платить цену за повторный запуск этих тестов с нуля.

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

Мартейн Питерс
источник
7

Вы можете достичь именно того, что вы хотите, без каких-либо изменений кода с помощью pytest --pdb .

С вашим примером:

import pytest
def test_abc():
    a = 9
    assert a == 10, "some error message"

Запустите с --pdb:

py.test --pdb
collected 1 item

test_abc.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    def test_abc():
        a = 9
>       assert a == 10, "some error message"
E       AssertionError: some error message
E       assert 9 == 10

test_abc.py:4: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /private/tmp/a/test_abc.py(4)test_abc()
-> assert a == 10, "some error message"
(Pdb) p a
9
(Pdb)

Как только тест не пройден, вы можете отладить его с помощью встроенного отладчика Python. Если вы закончили отладку, вы можете выполнить continueостальные тесты.

gnvk
источник
Остановится ли это, если контрольный пример не пройден или шаг теста не пройден.
Nitesh
Пожалуйста, проверьте связанные документы: doc.pytest.org/en/latest/…
gnvk
Отличная идея. Но если использовать --pdb, testcase будет останавливаться при каждом сбое. могу ли я решить во время выполнения, при каком сбое я хочу приостановить тестовый случай
Nitesh
5

Если вы используете PyCharm, вы можете добавить точку прерывания исключения, чтобы приостановить выполнение при сбое подтверждения. Выберите Просмотр точек останова (CTRL-SHIFT-F8) и добавьте обработчик исключений при повышении для AssertionError. Обратите внимание, что это может замедлить выполнение тестов.

В противном случае, если вы не возражаете против приостановки в конце каждого неудачного теста (непосредственно перед ошибкой), а не в тот момент, когда утверждение не выполняется, у вас есть несколько вариантов. Однако обратите внимание, что к этому моменту уже мог быть запущен различный код очистки, такой как закрытие файлов, которые были открыты в тесте. Возможные варианты:

  1. Вы можете указать pytest, чтобы он выводил вас в отладчик при ошибках, используя параметр --pdb .

  2. Вы можете определить следующий декоратор и украсить им каждую соответствующую тестовую функцию. (Помимо регистрации сообщения, вы также можете запустить pdb.post_mortem в этот момент или даже интерактивный код. Взаимодействовать с локальными кадрами кадра, где возникло исключение, как описано в этом ответе .)

from functools import wraps

def pause_on_assert(test_func):
    @wraps(test_func)
    def test_wrapper(*args, **kwargs):
        try:
            test_func(*args, **kwargs)
        except AssertionError as e:
            tkinter.messagebox.showinfo(e)
            # re-raise exception to make the test fail
            raise
    return test_wrapper

@pause_on_assert
def test_abc()
    a = 10
    assert a == 2, "some error message"
  1. Если вы не хотите вручную декорировать каждую тестовую функцию, вы можете вместо этого определить автоматическое устройство, которое проверяет sys.last_value :
import sys

@pytest.fixture(scope="function", autouse=True)
def pause_on_assert():
    yield
    if hasattr(sys, 'last_value') and isinstance(sys.last_value, AssertionError):
        tkinter.messagebox.showinfo(sys.last_value)
Ури Гранта
источник
Мне понравился ответ с декораторами, но это не может быть сделано динамически. Я хочу динамически контролировать, когда я хочу pause_on_assert или нет. Есть ли решение для этого?
Nitesh
Динамически каким образом? Как в одном выключателе включить / отключить его везде? Или какой-то способ контролировать это для каждого теста?
Ури Гранта
Предположим, у меня есть несколько тестов. В середине я получил необходимость сделать паузу при неудаче. Я включу переключатель. Спустя какое-то время я чувствую, что мне нужно отключить переключатель.
Nitesh
Ваш декоратор в ответ: 2 не будет работать для меня, потому что мой тестовый случай будет иметь несколько
утверждений
Что касается «переключателя», вы можете обновить любую реализацию pause_on_assertдля чтения из файла, чтобы решить, делать ли паузу или нет.
Ури Гранта
4

Одним из простых решений, если вы хотите использовать код Visual Studio, может быть использование условных точек останова .

Это позволит вам настроить ваши утверждения, например:

import pytest
def test_abc():
    a = 10
    assert a == 10, "some error message"

Затем добавьте условную точку останова в строку подтверждения, которая будет прерываться только при сбое утверждения:

введите описание изображения здесь

Ник Мартин
источник
@ Nitesh - я думаю, что это решение решает все ваши проблемы, вы ломаете только тогда, когда утверждение не удается, вы можете отладить код тут же и потом продолжить с оставшимися тестами ... Хотя это немного сложнее настроить Первоначально
Ник Мартин