Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Test-Driven Development (TDD)
Test-Driven Development (TDD) — это методология разработки, где тесты пишутся ДО написания кода. Это радикально отличается от традиционного подхода, где тесты пишутся после кода (если вообще пишутся).
Цикл TDD: Red-Green-Refactor
1. RED → Написать падающий тест (код не написан)
2. GREEN → Написать минимальный код для прохождения теста
3. REFACTOR → Улучшить код, сохраняя тесты зелёными
Поэтапный пример
Представим, нужно написать функцию для вычисления факториала:
Шаг 1: RED — Напишем тесты
# tests/test_factorial.py
import pytest
from factorial import calculate_factorial
class TestFactorial:
def test_factorial_of_zero(self):
"""Факториал 0 = 1"""
assert calculate_factorial(0) == 1
def test_factorial_of_one(self):
"""Факториал 1 = 1"""
assert calculate_factorial(1) == 1
def test_factorial_of_five(self):
"""Факториал 5 = 120"""
assert calculate_factorial(5) == 120
def test_factorial_of_negative_raises_error(self):
"""Отрицательное число вызывает ошибку"""
with pytest.raises(ValueError):
calculate_factorial(-1)
# Запускаем тесты: pytest test_factorial.py
# RESULT: ✗ FAILED (функция не существует)
Шаг 2: GREEN — Напишем минимальный код
# factorial.py
def calculate_factorial(n: int) -> int:
"""Вычислить факториал числа n"""
if n < 0:
raise ValueError("Факториал отрицательного числа не определён")
if n == 0 or n == 1:
return 1
result = 1
for i in range(2, n + 1):
result *= i
return result
# Запускаем тесты: pytest test_factorial.py
# RESULT: ✓ PASSED (все тесты зелёные)
Шаг 3: REFACTOR — Улучшим код
# factorial.py
import math
def calculate_factorial(n: int) -> int:
"""Вычислить факториал числа n"""
if n < 0:
raise ValueError("Факториал отрицательного числа не определён")
# Использование встроенной функции (более эффективно)
return math.factorial(n)
# Запускаем тесты: pytest test_factorial.py
# RESULT: ✓ PASSED (тесты всё ещё зелёные)
Реальный пример: функция поиска пользователя в БД
RED: Пишем тесты
# tests/test_user_service.py
import pytest
from unittest.mock import Mock
from user_service import UserService
class TestUserService:
def test_find_existing_user(self):
"""Поиск существующего пользователя"""
mock_db = Mock()
mock_db.query.return_value = {"id": 1, "name": "Alice"}
service = UserService(mock_db)
user = service.find_user(1)
assert user["name"] == "Alice"
mock_db.query.assert_called_once_with("SELECT * FROM users WHERE id=1")
def test_find_non_existing_user(self):
"""Поиск несуществующего пользователя"""
mock_db = Mock()
mock_db.query.return_value = None
service = UserService(mock_db)
user = service.find_user(999)
assert user is None
def test_invalid_user_id_raises_error(self):
"""Некорректный ID вызывает ошибку"""
mock_db = Mock()
service = UserService(mock_db)
with pytest.raises(ValueError):
service.find_user(-1)
GREEN: Пишем код
# user_service.py
class UserService:
def __init__(self, db):
self.db = db
def find_user(self, user_id: int):
"""Найти пользователя по ID"""
if user_id <= 0:
raise ValueError("ID должно быть положительным")
return self.db.query(f"SELECT * FROM users WHERE id={user_id}")
REFACTOR: Улучшаем код
# user_service.py
from typing import Optional, Dict
class UserService:
def __init__(self, db):
self.db = db
def find_user(self, user_id: int) -> Optional[Dict]:
"""Найти пользователя по ID
Args:
user_id: ID пользователя (должно быть > 0)
Returns:
Словарь с данными пользователя или None
Raises:
ValueError: Если user_id <= 0
"""
if user_id <= 0:
raise ValueError("ID должно быть положительным")
# Используем параметризованный запрос для защиты от SQL injection
return self.db.query("SELECT * FROM users WHERE id = @user_id", user_id=user_id)
Преимущества TDD
# 1. Спецификация кода (документация)
# Тесты показывают, КАК использовать функцию
user = service.find_user(1)
assert user["name"] == "Alice"
# 2. Дизайн более гибкий
# Пишем код с учётом тестируемости (dependency injection)
class UserService:
def __init__(self, db): # Инъекция зависимости
self.db = db
# 3. Меньше багов
# Баги ловятся на этапе разработки
# 4. Рефакторинг без страха
# Изменили код → тесты говорят что-то сломалось
# 5. Примеры использования
# Тесты — это примеры использования API
Best Practices TDD
# ✅ Одна проверка на тест
def test_addition():
assert add(2, 3) == 5
# ❌ Несколько проверок
def test_math():
assert add(2, 3) == 5
assert subtract(5, 3) == 2
assert multiply(3, 4) == 12
# ✅ Понятные имена
def test_calculate_discount_for_premium_users():
pass
# ❌ Непонятные имена
def test_calc():
pass
# ✅ Arrange-Act-Assert (AAA)
def test_user_promotion():
# Arrange — подготовка
user = User(role="user")
# Act — действие
user.promote_to_admin()
# Assert — проверка
assert user.role == "admin"
Процент покрытия тестами
ТЛ;ДР: 80-90% покрытия — оптимально
- 0-20% — не тестируется, высокий риск
- 50-70% — неплохо, но пропущены важные сценарии
- 80-90% — хорошо, охватывает основное
- 95-100% — возможно оверкодирование
Когда использовать TDD
# ✅ Используй TDD для:
- Критичного бизнес-логики (платежи, авторизация)
- API endpoints (контракты)
- Сложных алгоритмов (поиск, сортировка)
- Кода, который часто меняется (более устойчив к рефакторингу)
# ❌ Не нужен TDD для:
- Быстрого прототипа (MVP)
- UI компонентов (медленные тесты)
- Временного кода
Ключевые моменты
- RED → пишем падающий тест
- GREEN → пишем минимальный код
- REFACTOR → улучшаем, не ломая тесты
- Тесты — спецификация кода
- Дизайн лучше с учётом тестируемости
- Меньше багов и больше уверенности
- Документация живая — тесты не устаревают