Как повторно запускать неудачные тесты и сохранять состояние между запусками тестов

Использование

Плагин предоставляет две опции командной строки для повторного запуска сбоев с последнего вызова pytest:

  • --lf, --last-failed - повторное выполнение только сбоев.

  • --ff, --failed-first - для запуска сначала сбоев, а затем остальных тестов.

Для очистки (обычно это не требуется) опция --cache-clear позволяет удалить все содержимое межсессионного кэша перед запуском теста.

Другие плагины могут обращаться к объекту config.cache для установки/получения json-кодируемых значений между вызовами pytest.

Примечание

Этот плагин включен по умолчанию, но при необходимости может быть отключен: см. Деактивация / снятие с регистрации плагина по имени (внутреннее название этого плагина cacheprovider).

Повторное выполнение только отказов или сначала отказов

Сначала создадим 50 тестовых вызовов, из которых только 2 окажутся неудачными:

# content of test_50.py
import pytest


@pytest.mark.parametrize("i", range(50))
def test_num(i):
    if i in (17, 25):
        pytest.fail("bad luck")

Если вы запустите его в первый раз, вы увидите два сбоя:

$ pytest -q
.................F.......F........................                   [100%]
================================= FAILURES =================================
_______________________________ test_num[17] _______________________________

i = 17

    @pytest.mark.parametrize("i", range(50))
    def test_num(i):
        if i in (17, 25):
>           pytest.fail("bad luck")
E           Failed: bad luck

test_50.py:7: Failed
_______________________________ test_num[25] _______________________________

i = 25

    @pytest.mark.parametrize("i", range(50))
    def test_num(i):
        if i in (17, 25):
>           pytest.fail("bad luck")
E           Failed: bad luck

test_50.py:7: Failed
========================= short test summary info ==========================
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
2 failed, 48 passed in 0.12s

Если вы затем запустите его с --lf:

$ pytest --lf
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
run-last-failure: rerun previous 2 failures

test_50.py FF                                                        [100%]

================================= FAILURES =================================
_______________________________ test_num[17] _______________________________

i = 17

    @pytest.mark.parametrize("i", range(50))
    def test_num(i):
        if i in (17, 25):
>           pytest.fail("bad luck")
E           Failed: bad luck

test_50.py:7: Failed
_______________________________ test_num[25] _______________________________

i = 25

    @pytest.mark.parametrize("i", range(50))
    def test_num(i):
        if i in (17, 25):
>           pytest.fail("bad luck")
E           Failed: bad luck

test_50.py:7: Failed
========================= short test summary info ==========================
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
============================ 2 failed in 0.12s =============================

Вы запустили только два неудачных теста из последнего запуска, а 48 проходящих тестов не были запущены («отменены»).

Теперь, если вы запустите с опцией --ff, будут выполнены все тесты, но сначала будет выполнен первый предыдущий сбой (как видно из серии FF и точек):

$ pytest --ff
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 50 items
run-last-failure: rerun previous 2 failures first

test_50.py FF................................................        [100%]

================================= FAILURES =================================
_______________________________ test_num[17] _______________________________

i = 17

    @pytest.mark.parametrize("i", range(50))
    def test_num(i):
        if i in (17, 25):
>           pytest.fail("bad luck")
E           Failed: bad luck

test_50.py:7: Failed
_______________________________ test_num[25] _______________________________

i = 25

    @pytest.mark.parametrize("i", range(50))
    def test_num(i):
        if i in (17, 25):
>           pytest.fail("bad luck")
E           Failed: bad luck

test_50.py:7: Failed
========================= short test summary info ==========================
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
======================= 2 failed, 48 passed in 0.12s =======================

Опции New --nf, --new-first: запускать сначала новые тесты, а затем остальные, в обоих случаях тесты также сортируются по времени изменения файла, причем первыми идут более свежие файлы.

Поведение при отсутствии неудачных тестов в последнем запуске

Если при последнем запуске ни один тест не прошел, или не были найдены кэшированные данные lastfailed, pytest можно настроить либо на запуск всех тестов, либо ни одного теста, используя опцию --last-failed-no-failures, которая принимает одно из следующих значений:

pytest --last-failed --last-failed-no-failures all    # run all tests (default behavior)
pytest --last-failed --last-failed-no-failures none   # run no tests and exit

Новый объект config.cache

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

# content of test_caching.py
import pytest


def expensive_computation():
    print("running expensive computation...")


@pytest.fixture
def mydata(request):
    val = request.config.cache.get("example/value", None)
    if val is None:
        expensive_computation()
        val = 42
        request.config.cache.set("example/value", val)
    return val


def test_function(mydata):
    assert mydata == 23

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

$ pytest -q
F                                                                    [100%]
================================= FAILURES =================================
______________________________ test_function _______________________________

mydata = 42

    def test_function(mydata):
>       assert mydata == 23
E       assert 42 == 23

test_caching.py:19: AssertionError
-------------------------- Captured stdout setup ---------------------------
running expensive computation...
========================= short test summary info ==========================
FAILED test_caching.py::test_function - assert 42 == 23
1 failed in 0.12s

Если вы запустите его во второй раз, значение будет получено из кэша и ничего не будет напечатано:

$ pytest -q
F                                                                    [100%]
================================= FAILURES =================================
______________________________ test_function _______________________________

mydata = 42

    def test_function(mydata):
>       assert mydata == 23
E       assert 42 == 23

test_caching.py:19: AssertionError
========================= short test summary info ==========================
FAILED test_caching.py::test_function - assert 42 == 23
1 failed in 0.12s

Более подробную информацию см. в config.cache fixture.

Проверка содержимого кэша

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

$ pytest --cache-show
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
cachedir: /home/sweet/project/.pytest_cache
--------------------------- cache values for '*' ---------------------------
cache/lastfailed contains:
  {'test_caching.py::test_function': True}
cache/nodeids contains:
  ['test_caching.py::test_function']
cache/stepwise contains:
  []
example/value contains:
  42

========================== no tests ran in 0.12s ===========================

--cache-show принимает необязательный аргумент для указания шаблона glob для фильтрации:

$ pytest --cache-show example/*
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
cachedir: /home/sweet/project/.pytest_cache
----------------------- cache values for 'example/*' -----------------------
example/value contains:
  42

========================== no tests ran in 0.12s ===========================

Очистка содержимого кэша

Вы можете дать команду pytest очистить все файлы и значения кэша, добавив опцию --cache-clear следующим образом:

pytest --cache-clear

Это рекомендуется для вызовов с серверов непрерывной интеграции, где изоляция и корректность важнее скорости.

Пошаговая

В качестве альтернативы --lf -x, особенно для случаев, когда вы ожидаете, что большая часть набора тестов потерпит неудачу, --sw, --stepwise позволяет вам исправлять их по одному за раз. Набор тестов будет выполняться до первого сбоя, а затем остановится. При следующем вызове тесты продолжатся с последнего неудачного теста и будут выполняться до следующего неудачного теста. Вы можете использовать опцию --stepwise-skip, чтобы проигнорировать один сбойный тест и остановить выполнение теста на втором сбойном тесте. Это полезно, если вы застряли на неудачном тесте и хотите просто проигнорировать его до более позднего времени. Предоставление опции --stepwise-skip также неявно включит опцию --stepwise.

Вернуться на верх