Какие декораторы используются для тестирования в Python с помощью pytest

Ответ

Декораторы в pytest и других фреймворках позволяют декларативно управлять поведением тестов: параметризовать их, пропускать, помечать или подготавливать для них данные (фикстуры).

Ключевые декораторы:

  1. @pytest.fixture — определяет фикстуру, функцию для подготовки тестового окружения (например, соединение с БД, создание временных файлов) и его очистки после выполнения теста.

    import pytest
    
    @pytest.fixture(scope="module")
    def db_connection():
        # Код до yield выполняется перед тестами
        conn = create_connection()
        yield conn
        # Код после yield выполняется после тестов
        conn.close()
  2. @pytest.mark.parametrize — позволяет запустить один и тот же тест с разными наборами входных данных, что избавляет от дублирования кода.

    @pytest.mark.parametrize("test_input, expected", [
        (2, 4),
        (3, 9),
        (4, 16)
    ])
    def test_square(test_input, expected):
        assert test_input ** 2 == expected
  3. @pytest.mark.skip и @pytest.mark.skipif — используются для пропуска тестов. skip пропускает тест безусловно, а skipif — при выполнении определенного условия.

    import sys
    
    @pytest.mark.skip(reason="Тест еще не реализован")
    def test_new_feature():
        pass
    
    @pytest.mark.skipif(sys.version_info < (3, 10), reason="Требуется Python 3.10+")
    def test_feature_for_python310():
        pass
  4. @pytest.mark.xfail — помечает тест как "ожидаемо падающий". Тест будет запущен, но его падение не будет считаться ошибкой сборки. Это полезно для отслеживания известных багов.

    @pytest.mark.xfail(reason="Известный баг в API #123")
    def test_broken_api_call():
        assert call_api() is False
  5. @mock.patch (из unittest.mock) — широко используется вместе с pytest для мокирования (подмены) объектов, функций или классов во время теста, чтобы изолировать тестируемый код от внешних зависимостей.

    from unittest import mock
    
    @mock.patch("mymodule.external_service")
    def test_with_mock(mock_service):
        mock_service.get_data.return_value = {"status": "ok"}
        # ... вызов функции, использующей external_service

Ответ 18+ 🔞

А, ну вот, опять про эти ваши декораторы в питоне, блядь. Слушай, ну это же просто пиздец какой-то удобный инструмент, ёпта! Сидишь такой, пишешь тесты, а они тебе как обезьянки — прыгают, выполняются, а ты только свистишь им, маркируешь, хуяришь данные. Вообще красота, ядрёна вошь!

Ну вот смотри, самый главный, наверное, декоратор — это @pytest.fixture. Это типа такая заготовка, подготовка, блядь. Представь, тебе перед каждым тестом надо базу данных подключить. Ну и что, в каждом тесте писать connect, close? Да ты с ума сошёл! Вот ты её один раз описываешь как фикстуру, а потом просто имя её в параметры теста пишешь — и она тебе автоматом подсунута, вся готовая. А после теста сама закроется, чих-пых тебя в сраку! Удобно же, блядь!

import pytest

@pytest.fixture(scope="module")
def db_connection():
    # Код до yield выполняется перед тестами
    conn = create_connection()
    yield conn
    # Код после yield выполняется после тестов
    conn.close()

Дальше, овердохуище полезная штука — @pytest.mark.parametrize. Ну это вообще магия, блядь! У тебя есть одна функция-тест, а проверить надо кучу случаев. Раньше бы ты цикл написал или кучу одинаковых функций. А тут — на, получай! Навесил декоратор, перечислил в нём все варианты данных — и поехали. Питон сам размножит этот тест на столько копий, сколько ты ему пар задал. Гениально и просто, как тапок!

@pytest.mark.parametrize("test_input, expected", [
    (2, 4),
    (3, 9),
    (4, 16)
])
def test_square(test_input, expected):
    assert test_input ** 2 == expected

А вот эти @pytest.mark.skip и @pytest.mark.skipif — ну, тут всё понятно, хитрая жопа. Первый — просто «не запускай эту херню, я её ещё не допилил». Второй — «запускай, но только если версия питона не древнее, чем мои тапки». Очень помогает, когда работаешь в команде и у всех разное окружение, блядь. Не надо ничего комментить или удалять — просто пометил и всё, тест молча игнорируется.

import sys

@pytest.mark.skip(reason="Тест еще не реализован")
def test_new_feature():
    pass

@pytest.mark.skipif(sys.version_info < (3, 10), reason="Требуется Python 3.10+")
def test_feature_for_python310():
    pass

@pytest.mark.xfail — это вообще песня, блядь! Это когда ты знаешь, что тест сейчас упадёт, потому что там баг известный, тикет висит, фиксить будут через месяц. Но ты не хочешь его удалять или пропускать — вдруг починят и он начнёт проходить? Вот ты его помечаешь как «ожидаемо падающий». Он запустится, упадёт, но это не сломает всю сборку, не вызовет панику у тимлида. А как только баг починят — тест начнёт проходить и маркер xfail сработает как «неожиданный успех», и тебе прилетит уведомление, что пора маркер убирать. Хитро, сука!

@pytest.mark.xfail(reason="Известный баг в API #123")
def test_broken_api_call():
    assert call_api() is False

Ну и куда же без моков, блядь! @mock.patch — это из арсенала unittest.mock, но в питесте его тоже юзают на ура. Это когда твой код лезет куда-то в интернет, в другую систему, в сложную библиотеку, а тебе надо проверить только свою логику. Так ты эту внешнюю хрень подменяешь своей куклой, которая делает ровно то, что тебе нужно для теста. Типа, «эй, функция external_service, когда тебя вызовут — верни вот этот словарик и заткнись». И всё, твой код отработает в идеальной изоляции, как в барокамере. Красота, ёпта!

from unittest import mock

@mock.patch("mymodule.external_service")
def test_with_mock(mock_service):
    mock_service.get_data.return_value = {"status": "ok"}
    # ... вызов функции, использующей external_service

Вот так вот, блядь. Кажется, мелочь — какие-то собачки с @, а на самом деле это целая философия, как писать тесты, которые не сосут лапу, а реально помогают жить, а не ебать мозг. Освоишь это — и тестирование из каторги превратится почти в удовольствие. Ну, почти, блядь.