Известный баг исправили, а тест молчит: включаем strict для xfail

В наборе есть тест известной ошибки нормализации имени. Исправление отложили, и тест пометили xfail. Через несколько недель общий рефакторинг устранил дефект, но метка осталась. Тест теперь проходит неожиданно, однако обычный XPASS по умолчанию не делает запуск неуспешным. Команда продолжает считать сценарий сломанным и поддерживает ненужный обход в другом месте.

strict=True превращает неожиданный успех в ошибку набора. Это сигнал пересмотреть метку и связанные предположения. Известная проблема остаётся исполняемым ожиданием, а не выключенным фрагментом. В отличие от skip, xfail по умолчанию всё же запускает тест и наблюдает результат.

Известный дефект остаётся тестом

Пример специально содержит функцию с дефектом: пробелы по краям остаются. Мы описываем желаемый результат, указываем причину временной метки и ограничиваем ожидаемую ошибку AssertionError. При другом исключении тест не должен выглядеть как знакомый дефект нормализации.

import pytest

def normalize(value: str) -> str:
    return value.lower()

@pytest.mark.xfail(
    strict=True,
    raises=AssertionError,
    reason="normalization must remove surrounding spaces",
)
def test_surrounding_spaces():
    assert normalize(" Ann ") == "ann"

Неожиданный успех требует действия

Установите pytest командой python -m pip install pytest и запустите python -m pytest -rx test_name.py. Получится один XFAIL. Затем исправьте функцию на return value.strip().lower(). Повторный запуск завершится неуспешно с XPASS(strict). Теперь удалите метку xfail и снова запустите тест: он должен стать обычным проходящим регрессионным тестом.

Параметр raises важен для точности. Без ограничения внезапный AttributeError в середине подготовки может быть принят как ожидаемый провал. Но не расширяйте список исключений на всякий случай. Смысл метки — описать конкретный известный дефект. Если проблема зависит от версии зависимости или платформы, добавьте условие xfail и оставьте нормальный запуск в остальных окружениях.

Не ставьте xfail на целый модуль, когда известна одна комбинация параметров. Так можно скрыть новые ошибки в соседних сценариях. В параметризованном тесте метку удобнее прикрепить к отдельному pytest.param. Причина должна помогать найти решение или задачу на исправление; формулировка «пока не работает» быстро теряет смысл.

Для проекта, который регулярно использует такие метки, можно включить xfail_strict = true в конфигурации pytest. Сначала проверьте существующие XPASS и решите их явно. Неожиданное исправление заслуживает внимания так же, как неожиданное падение: меняется поведение программы, а старое исключение из правил больше не нужно. После удаления метки тест продолжает защищать исправление без специальных условий.

Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.

Посмотреть программу и практику курса.

Документация: официальный источник по теме.

Другие задачи по теме: Типы, тесты и поведение программы.

Продолжить на практике

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.