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

Что такое test driven development?

2.0 Middle🔥 181 комментариев
#Тестирование

Комментарии (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 → улучшаем, не ломая тесты
  • Тесты — спецификация кода
  • Дизайн лучше с учётом тестируемости
  • Меньше багов и больше уверенности
  • Документация живая — тесты не устаревают
Что такое test driven development? | PrepBro