Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Parametrize: один тест — десятки сценариев
parametrize_basics

Parametrize: один тест — десятки сценариев

@pytest.mark.parametrize, ids для читаемых названий, множественная параметризация

Представьте: нужно протестировать функцию validate_email() с 10 разными email'ами. Копировать тест 10 раз?

Цель: научиться использовать @pytest.mark.parametrize для тестирования множества сценариев одним тестом.

Проблема: копипаста тестов

Плохой подход: 5 одинаковых тестов

# ❌ ПЛОХО — повторяем один и тот же тест def test_add_positive_numbers(): assert add(2, 3) == 5 def test_add_zeros(): assert add(0, 0) == 0 def test_add_negative_numbers(): assert add(-1, -2) == -3 def test_add_mixed_numbers(): assert add(-1, 5) == 4 def test_add_large_numbers(): assert add(1000, 2000) == 3000

Проблемы:

  • ❌ Много дублирования
  • ❌ Сложно добавить новый тест-кейс
  • ❌ Если логика теста меняется — нужно править 5 мест

Решение: @pytest.mark.parametrize

Базовый пример

# ✅ ХОРОШО — один тест, 5 сценариев import pytest @pytest.mark.parametrize("a, b, expected", [ (2, 3, 5), (0, 0, 0), (-1, -2, -3), (-1, 5, 4), (1000, 2000, 3000), ]) def test_add(a, b, expected): """Тест сложения с разными числами""" result = add(a, b) assert result == expected

Что делает pytest:

  1. Читает список параметров [(2, 3, 5), (0, 0, 0), ...]
  2. Для КАЖДОГО набора запускает тест
  3. Если один тест падает — остальные всё равно запускаются

Запускаем:

pytest test_calculator.py::test_add -v

Результат:

test_add[2-3-5] PASSED                                               [ 20%]
test_add[0-0-0] PASSED                                               [ 40%]
test_add[-1--2--3] PASSED                                            [ 60%]
test_add[-1-5-4] PASSED                                              [ 80%]
test_add[1000-2000-3000] PASSED                                      [100%]

============================= 5 passed in 0.01s =============================

✅ Один тест превратился в 5 отдельных!

Синтаксис parametrize

@pytest.mark.parametrize("параметры", [ (значение1_для_параметра1, значение1_для_параметра2, ...), (значение2_для_параметра1, значение2_для_параметра2, ...), ]) def test_функция(параметр1, параметр2, ...): pass

Пример с одним параметром:

@pytest.mark.parametrize("number", [1, 2, 3, 4, 5]) def test_is_positive(number): """Все числа положительные""" assert number > 0

Пример с тремя параметрами:

@pytest.mark.parametrize("username, password, expected_success", [ ("admin", "secret123", True), ("user", "wrongpass", False), ("", "nouser", False), ]) def test_login(username, password, expected_success): result = login(username, password) assert result.success == expected_success

Понятные названия: ids parameter

Проблема: нечитаемые названия

По умолчанию pytest генерирует названия из значений:

test_validate_email[test@example.com] PASSED test_validate_email[invalid.com] FAILED

Для простых значений — ОК. Но для сложных объектов:

test_user[<User object at 0x7f...>] PASSED # Непонятно!

Решение: ids для читаемости

import pytest @pytest.mark.parametrize("email, is_valid", [ ("test@example.com", True), ("user.name@domain.co.uk", True), ("invalid.com", False), ("@nodomain", False), ("no-at-sign.com", False), ], ids=[ "valid-simple", "valid-subdomain", "missing-at", "missing-username", "missing-domain", ]) def test_validate_email(email, is_valid): """Валидация email'ов""" result = validate_email(email) assert result == is_valid

Результат:

test_validate_email[valid-simple] PASSED [ 20%] test_validate_email[valid-subdomain] PASSED [ 40%] test_validate_email[missing-at] FAILED [ 60%]

✅ Теперь понятно какой тест-кейс упал!

ids как функция (для сложных случаев)

def idfn(test_input): """Генерирует id из входных данных""" if test_input.startswith("valid"): return f"valid-case-{test_input[:10]}" return f"invalid-case-{test_input[:10]}" @pytest.mark.parametrize("email", [ "valid@example.com", "invalid.com", ], ids=idfn) def test_email(email): pass

Практический пример: BankAccount

import pytest from src.bank_account import BankAccount @pytest.mark.parametrize("initial, deposit, expected", [ (100, 50, 150), (0, 100, 100), (1000, 1, 1001), (50, 50, 100), ], ids=[ "normal-deposit", "deposit-to-empty", "small-deposit-to-large", "equal-amounts", ]) def test_deposit_scenarios(initial, deposit, expected): """Различные сценарии пополнения""" # Arrange account = BankAccount(initial) # Act new_balance = account.deposit(deposit) # Assert assert new_balance == expected, ( f"Deposit {deposit} to {initial} should give {expected}, " f"got {new_balance}" )

Тестируем ошибки

@pytest.mark.parametrize("initial, amount, error_msg", [ (100, -50, "must be positive"), (100, 0, "must be positive"), ], ids=[ "negative-deposit", "zero-deposit", ]) def test_invalid_deposit(initial, amount, error_msg): """Невалидные пополнения должны выбрасывать ошибку""" account = BankAccount(initial) with pytest.raises(ValueError) as exc_info: account.deposit(amount) assert error_msg in str(exc_info.value)

Множественная параметризация

Комбинации параметров

Можно применить @pytest.mark.parametrize ДВАЖДЫ:

@pytest.mark.parametrize("x", [0, 1]) @pytest.mark.parametrize("y", [2, 3]) def test_combinations(x, y): """Тестирует все комбинации x и y""" assert x * y >= 0

Результат: 4 теста (2 значения x × 2 значения y)

test_combinations[2-0] PASSED  # x=0, y=2
test_combinations[2-1] PASSED  # x=1, y=2
test_combinations[3-0] PASSED  # x=0, y=3
test_combinations[3-1] PASSED  # x=1, y=3

Когда НЕ использовать parametrize

❌ Плохие случаи использования

# ❌ ПЛОХО — тесты проверяют РАЗНЫЕ концепции @pytest.mark.parametrize("operation", [ "deposit", "withdraw", "get_balance", ]) def test_bank_operations(operation): # Слишком разная логика для каждого случая pass
# ❌ ПЛОХО — только 1-2 сценария @pytest.mark.parametrize("value", [5]) # Зачем parametrize? def test_single_value(value): assert value == 5

✅ Хорошие случаи использования

  • ✅ Тестируете ОДНУ функцию с разными входными данными
  • ✅ Много похожих тест-кейсов (> 3)
  • ✅ Легко добавить новый сценарий (одна строка)
  • ✅ Все сценарии проверяют ОДНО поведение

Параметры как словарь

Когда кейсов много, удобнее хранить их в словаре и явно вытаскивать поля:

import pytest test_cases = { "valid_simple": { "email": "test@example.com", "is_valid": True, }, "invalid_no_at": { "email": "invalid.com", "is_valid": False, }, } @pytest.mark.parametrize( "email, is_valid", [(case["email"], case["is_valid"]) for case in test_cases.values()], ids=list(test_cases.keys()), ) def test_validate_email(email, is_valid): result = validate_email(email) assert result == is_valid

Что вы изучили

  • @pytest.mark.parametrize — один тест, много сценариев
  • ids parameter — понятные названия тестов
  • Множественная параметризация — матричное тестирование
  • Когда использовать — похожие тест-кейсы
  • Когда НЕ использовать — разная логика, мало кейсов

Устранение неисправностей

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: Маркеры: запускаем нужные тесты