Я использую утверждения 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, как упомянуто выше, потому что таким образом я должен измениться в количестве мест
assert
но пишите свои собственные функции проверки, которые делают то, что вы хотите.pytest
для ваших тестовых случаев. Он поддерживает использование assert и пропуск тестов вместе со многими другими функциями, которые облегчают написание тестовых наборов.assert cond, "msg"
в вашем коде_assertCustom("assert cond, 'msg'")
? Вероятно,sed
один вкладыш мог бы сделать это.Ответы:
Вы используете
pytest
, что дает вам широкие возможности для взаимодействия с ошибочными тестами. Это дает вам параметры командной строки и несколько хуков, чтобы сделать это возможным. Я объясню, как использовать каждый из них и где вы могли бы сделать настройки в соответствии с вашими конкретными потребностями отладки.Я также расскажу о более экзотических опциях, которые позволили бы вам полностью пропустить конкретные утверждения, если вы действительно этого хотите.
Обрабатывать исключения, а не утверждать
Обратите внимание, что провальный тест обычно не останавливает pytest; только если вы включили явное указание ему выйти после определенного количества сбоев . Кроме того, тесты не проходят, потому что возникает исключение;
assert
поднимает,AssertionError
но это не единственное исключение, которое приведет к провалу теста! Вы хотите контролировать обработку исключений, а не изменять ихassert
.Тем не менее, в случае неудачного утверждения будет завершен отдельный тест. Это потому, что как только исключение возникает за пределами
try...except
блока, Python разматывает текущий фрейм функции, и возвращаться к нему уже нельзя.Я не думаю, что это то, что вы хотите, судя по вашему описанию ваших
_assertCustom()
попыток повторно выполнить утверждение, но, тем не менее, я буду обсуждать ваши варианты ниже.Посмертная отладка в pytest с помощью pdb
Для различных вариантов обработки сбоев в отладчике я начну с
--pdb
переключателя командной строки , который открывает стандартное приглашение отладки при сбое теста (выходные данные исключены для краткости):С помощью этого переключателя, когда тест не пройден, pytest начинает сеанс отладки после смерти . По сути, это именно то, что вы хотели; чтобы остановить код в точке неудачного теста и открыть отладчик, чтобы посмотреть на состояние вашего теста. Вы можете взаимодействовать с локальными переменными теста, глобальными переменными, а также локальными и глобальными переменными каждого кадра в стеке.
Здесь pytest дает вам полный контроль над тем, выходить или нет после этой точки: если вы используете команду
q
quit, то pytest также завершает выполнение, использованиеc
для продолжения вернет управление в pytest и будет выполнен следующий тест.Использование альтернативного отладчика
Вы не связаны с
pdb
отладчиком для этого; Вы можете установить другой отладчик с помощью--pdbcls
переключателя. Будет работать любаяpdb.Pdb()
совместимая реализация, включая реализацию отладчика IPython или большинство других отладчиков Python ( отладчик pudb требует использования-s
переключателя или специального плагина ). Коммутатор принимает модуль и класс, например, для использованияpudb
вы можете использовать:Вы можете использовать эту функцию , чтобы написать свой собственный класс - обертку вокруг ,
Pdb
которая просто возвращает немедленно , если отказ конкретных не то , что вы заинтересованы в том ,pytest
используетPdb()
так же , какpdb.post_mortem()
делает :Здесь
t
находится объект трассировки . Когдаp.interaction(None, t)
возвращается,pytest
продолжается со следующим тестом, еслиp.quitting
не установлено значениеTrue
(после чего pytest затем завершается).Вот пример реализации, которая распечатывает, что мы отказываемся отлаживать, и возвращает немедленно, если только тест не был запущен
ValueError
и сохранен какdemo/custom_pdb.py
:Когда я использую это с вышеприведенной демонстрацией, это вывод (опять же, для краткости):
Вышеупомянутые интроспективы
sys.last_type
определяют, является ли неудача «интересной».Однако я не могу порекомендовать эту опцию, если вы не хотите написать свой собственный отладчик, используя tkInter или что-то подобное. Обратите внимание, что это большое начинание.
Фильтрация сбоев; выбрать, когда открыть отладчик
Следующий уровень является pytest отладки и взаимодействия крюки ; это точки подключения для настройки поведения, чтобы заменить или улучшить то, как pytest обычно обрабатывает такие вещи, как обработка исключения или вход в отладчик через
pdb.set_trace()
илиbreakpoint()
(Python 3.7 или новее).Внутренняя реализация этого хука также отвечает за печать
>>> entering PDB >>>
вышеупомянутого баннера, поэтому использование этого хука для предотвращения отладчика означает, что вы вообще не увидите этот вывод. Вы можете иметь свой собственный хук, а затем делегировать исходный хук, если неудачный тест «интересен», и таким образом фильтровать неудачи теста независимо от того, какой отладчик вы используете! Вы можете получить доступ к внутренней реализации, обратившись к ней по имени ; Внутренний подключаемый модуль для этого называетсяpdbinvoke
. Чтобы предотвратить его запуск, необходимо отменить его регистрацию, но сохранить ссылку. Мы можем вызывать ее напрямую по мере необходимости.Вот пример реализации такого хука; Вы можете поместить это в любое место, из которого загружаются плагины ; Я вставил это в
demo/conftest.py
:Данный плагин использует внутренний
TerminalReporter
плагин для записи строк в терминал; это делает вывод более чистым при использовании формата статуса компактного теста по умолчанию и позволяет записывать данные в терминал даже с включенным захватом вывода.В этом примере объект плагина регистрируется с
pytest_exception_interact
помощью ловушки через другую ловушку,pytest_configure()
но необходимо убедиться, что он работает достаточно поздно (используя@pytest.hookimpl(trylast=True)
), чтобы иметь возможность отменить регистрацию внутреннегоpdbinvoke
плагина. Когда вызывается ловушка, пример проверяетcall.exceptinfo
объект ; Вы также можете проверить узел или отчет тоже.С учетом указанных выше примере кода на месте в
demo/conftest.py
, тоtest_ham
сбой теста игнорируется, толькоtest_spam
ошибки теста, который поднимаетValueError
, приводит к быстрой отладки открытия:Чтобы повторить, вышеприведенный подход имеет дополнительное преимущество, заключающееся в том, что вы можете комбинировать его с любым отладчиком, который работает с pytest , включая pudb или отладчик IPython:
В нем также гораздо больше контекста о том, какой тест выполнялся (через
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.excinfo
. Этот атрибут имеет значение кортеж (тип, экземпляр, трассировка), если в тесте возникло исключение. В качестве альтернативы, вы можете позвонитьoutcome.get_result()
и использовать стандартнуюtry...except
обработку.Так как же пройти тестовый проход? У вас есть 3 основных варианта:
pytest.xfail()
оболочку.pytest.skip()
.outcome.force_result()
метод ; установить результат в пустой список здесь (то есть: зарегистрированный хук не дал ничего, кромеNone
), и исключение будет полностью очищено.То, что вы используете, зависит от вас. Не забудьте сначала проверить результаты пропущенных тестов и тестов с ожидаемым отказом, поскольку вам не нужно обрабатывать эти случаи, как если бы тест не прошел. Вы можете получить доступ к специальным исключениям, которые эти опции вызывают через
pytest.skip.Exception
иpytest.xfail.Exception
.Вот пример реализации, которая помечает неудачные тесты, которые не вызывают
ValueError
, как пропущенные :Когда положить в
conftest.py
выходной становится:Я использовал
-r a
флаг, чтобы прояснить, чтоtest_ham
было пропущено сейчас.Если вы замените
pytest.skip()
вызов наpytest.xfail("[XFAIL] ignoring everything but ValueError")
, тест помечается как ожидаемый сбой:и используя
outcome.force_result([])
пометки как пройденные:Вам решать, какой из них вы считаете наиболее подходящим для вашего варианта использования. Для
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
команду, чтобы пропустить само утверждение. Вы должны были бы сделать компромисс; запускать все тесты в отладчике (что медленно, поскольку интерпретатору приходится вызывать функцию трассировки для каждого оператора) или применять его только к ошибочным тестам и платить цену за повторный запуск этих тестов с нуля.Такой плагин было бы много работы, чтобы создать, я не собираюсь писать пример здесь, частично потому, что он не вписывается в ответ в любом случае, и частично, потому что я не думаю, что это стоит времени . Я бы просто открыл отладчик и сделал прыжок вручную. Неудачное утверждение указывает на ошибку либо в самом тесте, либо в тестируемом коде, так что вы можете просто сосредоточиться на устранении проблемы.
источник
Вы можете достичь именно того, что вы хотите, без каких-либо изменений кода с помощью pytest --pdb .
С вашим примером:
Запустите с --pdb:
Как только тест не пройден, вы можете отладить его с помощью встроенного отладчика Python. Если вы закончили отладку, вы можете выполнить
continue
остальные тесты.источник
Если вы используете PyCharm, вы можете добавить точку прерывания исключения, чтобы приостановить выполнение при сбое подтверждения. Выберите Просмотр точек останова (CTRL-SHIFT-F8) и добавьте обработчик исключений при повышении для AssertionError. Обратите внимание, что это может замедлить выполнение тестов.
В противном случае, если вы не возражаете против приостановки в конце каждого неудачного теста (непосредственно перед ошибкой), а не в тот момент, когда утверждение не выполняется, у вас есть несколько вариантов. Однако обратите внимание, что к этому моменту уже мог быть запущен различный код очистки, такой как закрытие файлов, которые были открыты в тесте. Возможные варианты:
Вы можете указать pytest, чтобы он выводил вас в отладчик при ошибках, используя параметр --pdb .
Вы можете определить следующий декоратор и украсить им каждую соответствующую тестовую функцию. (Помимо регистрации сообщения, вы также можете запустить pdb.post_mortem в этот момент или даже интерактивный код. Взаимодействовать с локальными кадрами кадра, где возникло исключение, как описано в этом ответе .)
источник
pause_on_assert
для чтения из файла, чтобы решить, делать ли паузу или нет.Одним из простых решений, если вы хотите использовать код Visual Studio, может быть использование условных точек останова .
Это позволит вам настроить ваши утверждения, например:
Затем добавьте условную точку останова в строку подтверждения, которая будет прерываться только при сбое утверждения:
источник