Какой порядок действий написания тестов для функции?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Порядок написания тестов для функции (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
Рекомендуемый процесс разработки
- Написать спецификацию — определить требования
- Написать тесты — описать все сценарии (нормальные и ошибочные)
- Запустить тесты — убедиться, что они падают (RED)
- Реализовать функцию — написать минимальный код (GREEN)
- Запустить тесты — проверить, что все проходят
- Рефакторить — улучшить качество кода
- Запустить тесты — убедиться, что они всё ещё проходят
- Достичь 90%+ покрытия — убедиться в качестве
Этот подход гарантирует, что твой код будет работать правильно, будет легче поддерживаться и модифицироваться в будущем.