Чи можливо змінити поведінку твердження твердження PyTest у Python


18

Я використовую заяви твердження Python, щоб відповідати фактичній та очікуваній поведінці. Я не маю контролю над ними, ніби є тестові випадки помилок абортів. Я хочу взяти під контроль помилку твердження і хочу визначити, чи хочу я відмінити тестовий зразок за твердженням про відмову чи ні.

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

Я не маю уявлення, як це зробити

Приклад коду, тут ми використовуємо pytest

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

Below is my expectation

Коли assert кидає asertionError, я повинен мати можливість призупинити тестовий зразок і можу налагоджувати та пізніше відновити. Для паузи та резюме я буду використовувати tkinterмодуль. Я зроблю функцію затвердження, як показано нижче

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)

Ідучи вперед, я повинен змінити кожне твердження твердження з цією функцією як

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: Наведений вище код ще не перевірений. Можуть бути і невеликі синтаксичні помилки

Редагувати: Дякую за відповіді. Подовживши це питання трохи попереду. Що робити, якщо я хочу змінити поведінку утвердження. В даний час, коли є випробувальна помилка твердження, виходить. Що робити, якщо я хочу вибрати, чи потрібен мені вихід з тестової скриньки при певній відмові утвердження чи ні. Я не хочу писати користувальницьку функцію затвердження, як згадувалося вище, тому що мені потрібно змінити кількість місць


3
Чи можете ви надати нам приклад коду того, що ви хочете зробити?
mrblewog

1
Не використовуйте, assertа пишіть власні функції перевірки, які роблять те, що ви хочете.
molbdnilo

Чому ви не вставляєте ствердження в блок " спробу " і повідомлення про помилку, крім ?
Prathik Kini

1
Це звучить як те, що ви дійсно хочете, це використовувати pytestдля своїх тестових випадків. Він підтримує використання тестів утвердження та пропускання, а також багатьох інших функцій, що спрощують написання тестових наборів.
blubberdiblub

1
Чи не було б досить просто написати простий інструмент, який механічно замінив би кожен assert cond, "msg"у вашому коді _assertCustom("assert cond, 'msg'")? Напевно, це sedвдалося зробити однолінійним.
NPE

Відповіді:


23

Ви використовуєте 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(у який момент піст-тест після цього закінчується).

Ось приклад реалізації, який показує, що ми відмовляємось від налагодження та повертаємось негайно, якщо тест не піднятий 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().

Коли піст-тест виконує тести, він виконуватиме тест через вищезазначений гак, який, як очікується, поверне Noneабо підвищить виняток. З цього створюється звіт, необов'язково створюється запис журналу, і якщо тест не вдався, pytest_exception_interact()викликається вищевказаний гак. Отже, все, що вам потрібно зробити, - це змінити результат, який дає цей гачок; замість винятку він просто не повинен взагалі нічого повертати.

Найкращий спосіб зробити це - використовувати обгортку для гаків . Обгортки з гаків не повинні виконувати фактичну роботу, а натомість надають можливість змінити те, що відбувається з результатом гачка. Все, що вам потрібно зробити - це додати рядок:

outcome = yield

у реалізації вашої обертової оболонки, і ви отримуєте доступ до результату гачка , включаючи тестовий виняток через outcome.excinfo. Цей атрибут встановлюється в кордоні (тип, екземпляр, зворотний зв'язок), якщо в тесті було піднято виняток. Крім того, ви можете зателефонувати outcome.get_result()та використовувати стандартне try...exceptкерування.

Тож як зробити пропускний тест? У вас є три основні варіанти:

  • Ви можете позначити тест як очікуваний збій, зателефонувавши 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 . Якщо ви це зробите, ви можете виправити цей модуль, щоб додати там свої власні модифікації, включаючи оточення assertз try...except AssertionError:обробником.

Однак ви не можете просто відключити або проігнорувати твердження вибірково, тому що наступні оператори можуть легко залежати від стану (конкретних об'єктних компонувань, встановлених змінних тощо), проти яких проголошене ствердження повинно було захищати. Якщо тестів на затвердження такого fooнемає None, то пізніший сертифікат покладається на foo.barіснування, то ви просто наткнетесь на AttributeErrorтуди і т. Д. Дотримуйтесь повторного підняття винятку, якщо вам потрібно пройти цей маршрут.

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

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

Використання налагоджувача для пропуску тверджень про твердження.

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

Ви навіть можете це автоматизувати. Використовуючи вищезазначені методи, ви можете створити спеціальний плагін для налагодження

  • використовує 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з рештою тестів.


Чи припиниться це, коли тестовий випадок не вдасться або крок тесту не вдасться.
Нітеш

Перевірте пов’язані документи: doc.pytest.org/en/latest/…
gnvk

Відмінна ідея. Але якщо використовувати --pdb, тестовий зразок буде робити паузу при кожному збої. чи можу я вирішити, під час виконання якого відмови я хочу призупинити тестовий випадок
Нітеш

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)

Мені сподобалась відповідь з декораторами, але це неможливо зробити динамічно. Я хочу динамічно керувати, коли я хочу зробити паузу_on_assert чи ні. Чи є для цього рішення?
Нітеш

Динамічним яким чином? Як в одному комутаторі, щоб увімкнути / вимкнути його скрізь? Або якийсь спосіб контролювати його для кожного тесту?
Урі Гранта

Припустимо, я запускаю кілька тестів. В середині я отримав потребу в паузі на відмову. Я вмикаю перемикач. Пізніше в будь-який момент часу я відчуваю, що мені потрібно відключити комутатор.
Нітеш

Ваш декоратор у відповідь: 2 не працюватиме для мене, оскільки мій тестовий випадок матиме декілька тверджень
Нітеш

Щодо "перемикача", ви можете оновити будь-яку реалізацію pause_on_assertдля читання з файлу, щоб вирішити, чи потрібно призупиняти чи ні.
Урі Гранта

4

Одним простим рішенням, якщо ви бажаєте використовувати код Visual Studio, може бути використання умовних точок перерви .

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

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

Потім додайте у рядку ствердження умовну точку розриву, яка порушиться лише тоді, коли ваше твердження не вдасться:

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


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