Skip to main content
Back to course
Pytest с нуля: тесты, которые реально работают
5 / 1145%

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

25 минут

⚠️ Урок устарел. Свежая версия в курсе pytest-basics: Parametrize: один тест — десятки сценариев.

Умножаем эффективность тестирования

⏱️ До: 10 похожих тестов = 50 строк кода, 10 минут на добавление кейса
После: 1 параметризованный тест = 10 строк кода, 30 секунд на новый кейс

✅ Один тест покрывает десятки сценариев с parametrize
✅ Понятные ids делают отчёт читаемым
✅ Ошибки проверяются так же, как happy path

Проблема: тесты-клоны

Пример копипасты, который нужно сократить:

# ❌ Дублирование — больно поддерживать
def test_add_simple_task():
    assert add_task([], "buy milk") == ["buy milk"]
 
def test_add_task_strips_whitespace():
    assert add_task([], "  learn pytest  ") == ["learn pytest"]
 
def test_add_task_with_special_chars():
    assert add_task([], "email@example.com") == ["email@example.com"]

Решение: один параметризованный тест

Переписываем в один параметризованный тест:

import pytest
 
@pytest.mark.parametrize(
    "input_text,expected_output",
    [
        ("buy milk", ["buy milk"]),
        ("  learn pytest  ", ["learn pytest"]),
        ("email@example.com", ["email@example.com"]),
    ],
)
def test_add_task_variations(input_text, expected_output):
    result = add_task([], input_text)
    assert result == expected_output

Выгода: меньше кода, проще добавлять кейсы, отчёт понятен.

Понятные имена через ids

Добавляем читаемые ids для отчёта:

@pytest.mark.parametrize(
    "input_text,expected_output",
    [
        ("buy milk", ["buy milk"]),
        ("  learn pytest  ", ["learn pytest"]),
    ],
    ids=["simple_text", "strip_whitespace"],  # 🏷️ читаемые названия в отчёте
)
def test_add_task_variations(input_text, expected_output):
    ...

Несколько параметров

Параметризация нескольких аргументов:

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
])
def test_add_numbers(a, b, expected):
    assert a + b == expected

Parametrize для ошибок

То же для ошибок через pytest.raises:

@pytest.mark.parametrize(
    "text,expected_exception",
    [
        ("", ValueError),
        (None, TypeError),
        ("   ", ValueError),
    ],
)
def test_add_task_errors(text, expected_exception):
    with pytest.raises(expected_exception):
        add_task([], text)

Практика: найдите баги с parametrize

Задача: почините валидацию email.

Практика: ловим баги в email-валидации одной таблицей кейсов:

# ❌ Баг: проверка слишком простая
def validate_email(email: str) -> bool:
    return "@" in email
 
import pytest
 
@pytest.mark.parametrize(
    "email,should_be_valid",
    [
        ("user@example.com", True),
        ("invalid", False),
        ("user@.com", False),            # 🎯 баг
        ("@example.com", False),         # 🎯 баг
        ("user@example.", False),        # 🎯 баг
        ("user+tag@example.com", True),
    ],
)
def test_validate_email_comprehensive(email, should_be_valid):
    actual_result = validate_email(email)
    assert actual_result == should_be_valid, f"Email: {email} | Expected: {should_be_valid}, Got: {actual_result}"

🚀 Шпаргалка: parametrize в действии

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

  • ✅ Похожие кейсы с разными входными данными
  • ✅ Граничные значения (0, отрицательные, очень большие)
  • ✅ Комбинации параметров (цвет × размер × материал)
  • ✅ Ошибки с разными невалидными входами

Мини-команды

Команды для быстрого запуска:

pytest -k "add_task" -v                     # Только нужные тесты
pytest -q --disable-warnings --maxfail=1    # Быстрый прогон с первым падением

Чеклист parametrize

  • Похожие тесты объединены в parametrize
  • Используются понятные ids
  • Граничные случаи покрыты рядом кейсов
  • Ошибки проверяются через parametrize + pytest.raises

Дополнительно: комбинации параметров (опционально)

Дополнительно: пример с комбинированными параметрами:

@pytest.mark.parametrize("status", ["active", "inactive"])
@pytest.mark.parametrize("role", ["user", "admin"])
def test_user_combinations(status, role):
    user = create_user(status=status, role=role)
    assert user.has_access() == (status == "active" and role == "admin")

Пишите тесты быстрее и точнее: меньше копипасты, больше покрытия и ясности. 🚀