← Назад к вопросам

Какой порядок действий написания тестов для функции?

2.0 Middle🔥 151 комментариев
#REST API и HTTP

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Порядок написания тестов для функции (TDD подход)

Правильный процесс написания тестов следует методологии Test-Driven Development (TDD). Это не просто тестирование готового кода, а проектирование функции через тесты.

Цикл TDD: Red-Green-Refactor

1. RED — Написать падающий тест

Сначала ты пишешь тест для функции, которая ещё не существует. Тест должен описывать ожидаемое поведение:

import pytest

def test_calculate_discount_no_discount():
    """Тест: при цене <= 100, скидка 0%"""
    result = calculate_discount(50)
    assert result == 50

def test_calculate_discount_with_discount():
    """Тест: при цене > 100, скидка 10%"""
    result = calculate_discount(200)
    assert result == 180

def test_calculate_discount_invalid_price():
    """Тест: отрицательная цена вызывает ошибку"""
    with pytest.raises(ValueError):
        calculate_discount(-50)

На этом этапе тесты падают — функция ещё не написана.

2. GREEN — Написать минимальный код для прохождения тестов

Теперь реализуешь функцию с минимальной логикой, достаточной для прохождения всех тестов:

def calculate_discount(price: float) -> float:
    """Вычисляет цену с учётом скидки"""
    if price < 0:
        raise ValueError("Цена не может быть отрицательной")
    
    if price > 100:
        return price * 0.9  # 10% скидка
    
    return price

Все тесты теперь проходят.

3. REFACTOR — Улучшить код, сохраняя тесты зелёными

Теперь, когда тесты проходят, можно улучшить качество кода без изменения поведения:

from enum import Enum

class PriceRange(Enum):
    STANDARD = (0, 100)
    PREMIUM = (100.01, float(inf))

DISCOUNT_RATE = 0.10

def calculate_discount(price: float) -> float:
    """Вычисляет цену с учётом скидки
    
    Args:
        price: Исходная цена (положительное число)
    
    Returns:
        Финальная цена с учётом скидки
    
    Raises:
        ValueError: Если цена отрицательная
    """
    if price < 0:
        raise ValueError("Цена не может быть отрицательной")
    
    if price > PriceRange.STANDARD.value[1]:
        return price * (1 - DISCOUNT_RATE)
    
    return price

Тесты всё ещё проходят.

Полный пример: Разработка функции валидации email

# Шаг 1: RED — Написать тесты
def test_validate_email_valid():
    """Корректный email должен пройти валидацию"""
    assert validate_email("user@example.com") is True

def test_validate_email_no_at_symbol():
    """Email без @ должен быть невалидным"""
    assert validate_email("userexample.com") is False

def test_validate_email_no_domain():
    """Email без домена должен быть невалидным"""
    assert validate_email("user@") is False

def test_validate_email_no_local_part():
    """Email без локальной части должен быть невалидным"""
    assert validate_email("@example.com") is False

def test_validate_email_empty_string():
    """Пустая строка должна быть невалидной"""
    assert validate_email("") is False

# Шаг 2: GREEN — Минимальная реализация
def validate_email(email: str) -> bool:
    if not email:
        return False
    if "@" not in email:
        return False
    local, domain = email.split("@")
    if not local or not domain:
        return False
    if "." not in domain:
        return False
    return True

# Шаг 3: REFACTOR — Улучшение
import re
from typing import Final

EMAIL_PATTERN: Final[str] = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"

def validate_email(email: str) -> bool:
    """Валидирует email адрес
    
    Args:
        email: Email для проверки
    
    Returns:
        True если email валиден, False иначе
    """
    if not isinstance(email, str):
        return False
    
    return bool(re.match(EMAIL_PATTERN, email.strip()))

Правила написания тестов

1. Структура: AAA Pattern (Arrange-Act-Assert)

def test_function_behavior():
    # Arrange — подготовка данных
    input_value = 10
    expected_result = 20
    
    # Act — выполнение функции
    actual_result = multiply(input_value, 2)
    
    # Assert — проверка результата
    assert actual_result == expected_result

2. Тестируй граничные случаи (Edge Cases)

def test_divide_by_zero():
    """Деление на 0 вызывает ошибку"""
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

def test_divide_negative_numbers():
    """Деление отрицательных чисел работает корректно"""
    assert divide(-10, 2) == -5

def test_divide_floats():
    """Деление дробных чисел работает корректно"""
    assert abs(divide(10.0, 3.0) - 3.333) < 0.001

3. Один тест = одна проверка

# ❌ Плохо
def test_user_creation():
    user = create_user("John", "john@example.com")
    assert user.name == "John"
    assert user.email == "john@example.com"
    assert user.created_at is not None

# ✅ Хорошо
def test_user_creation_sets_name():
    user = create_user("John", "john@example.com")
    assert user.name == "John"

def test_user_creation_sets_email():
    user = create_user("John", "john@example.com")
    assert user.email == "john@example.com"

def test_user_creation_sets_timestamp():
    user = create_user("John", "john@example.com")
    assert user.created_at is not None

Рекомендуемый процесс разработки

  1. Написать спецификацию — определить требования
  2. Написать тесты — описать все сценарии (нормальные и ошибочные)
  3. Запустить тесты — убедиться, что они падают (RED)
  4. Реализовать функцию — написать минимальный код (GREEN)
  5. Запустить тесты — проверить, что все проходят
  6. Рефакторить — улучшить качество кода
  7. Запустить тесты — убедиться, что они всё ещё проходят
  8. Достичь 90%+ покрытия — убедиться в качестве

Этот подход гарантирует, что твой код будет работать правильно, будет легче поддерживаться и модифицироваться в будущем.

Какой порядок действий написания тестов для функции? | PrepBro