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

Что такое TDD?

2.0 Middle🔥 151 комментариев
#MLOps и инфраструктура

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

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

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

TDD (Test-Driven Development)

TDD — методология разработки, при которой тесты пишут ДО написания кода. Цикл: RED → GREEN → REFACTOR.

Цикл TDD

1. RED: Напиши падающий тест

import pytest
from calculator import add

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

# Запускаем: pytest
# FAIL: ModuleNotFoundError: No module named 'calculator'

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

# calculator.py
def add(a, b):
    return a + b

# Запускаем: pytest
# PASSED: все тесты зелены

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

# Добавь валидацию, документацию, оптимизацию
def add(a: int, b: int) -> int:
    """Складывает два числа"""
    if not isinstance(a, (int, float)):
        raise TypeError(f"Expected number, got {type(a)}")
    if not isinstance(b, (int, float)):
        raise TypeError(f"Expected number, got {type(b)}")
    return a + b

# Запускаем: pytest
# PASSED: всё ещё работает

Пример: функция для валидации email

Шаг 1: RED (падающие тесты)

import pytest
from validator import is_valid_email

class TestEmailValidator:
    def test_valid_email(self):
        assert is_valid_email("user@example.com") == True
        assert is_valid_email("john.doe@company.co.uk") == True
    
    def test_invalid_email(self):
        assert is_valid_email("invalid.email") == False
        assert is_valid_email("@example.com") == False
        assert is_valid_email("user@") == False
    
    def test_empty_string(self):
        assert is_valid_email("") == False

# pytest test_validator.py
# FAILED: is_valid_email is not defined

Шаг 2: GREEN (минимальный код)

# validator.py
def is_valid_email(email: str) -> bool:
    return "@" in email and "." in email

# pytest
# 3 passed

Шаг 3: REFACTOR (улучшение)

import re

def is_valid_email(email: str) -> bool:
    """Проверяет валидность email адреса"""
    if not email or not isinstance(email, str):
        return False
    
    pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    return bool(re.match(pattern, email))

# pytest
# 3 passed (тесты всё еще проходят)

TDD в ML/Data Science

# Тест для функции обработки данных
def test_data_preprocessing():
    raw_data = pd.DataFrame({
        'age': [25, None, 35, 40],
        'salary': [50000, 60000, None, 80000]
    })
    
    processed = preprocess_data(raw_data)
    
    # Проверяем удаление пропусков
    assert processed.isnull().sum().sum() == 0
    # Проверяем размер
    assert len(processed) == 2
    # Проверяем типы
    assert processed['age'].dtype in [np.float64, np.int64]

# RED: функция не существует
# GREEN: минимальная реализация
def preprocess_data(df):
    return df.dropna()

# REFACTOR: добавь валидацию, логирование
def preprocess_data(df: pd.DataFrame) -> pd.DataFrame:
    """Удаляет строки с пропусками"""
    if df.empty:
        raise ValueError("Input DataFrame is empty")
    
    initial_rows = len(df)
    df_clean = df.dropna()
    dropped = initial_rows - len(df_clean)
    
    print(f"Removed {dropped} rows with missing values")
    return df_clean

Преимущества TDD

✓ Меньше багов: тесты ловят проблемы рано
✓ Документация: тесты показывают usage
✓ Рефакторинг: можешь менять код, не боясь
✓ Дизайн: думаешь о API перед реализацией
✓ Confidence: знаешь, что код работает

Недостатки TDD

✗ Медленнее писать: тесты + код
✗ Для exploratory работы плохо (notebooks)
✗ Трудно начать (нужна практика)
✗ Мертвые тесты: если требования меняются

TDD vs коде-сначала разработка

АспектTDDCode-First
Когда писатьТесты→КодКод→Тесты
Покрытие~95%+~40-60%
БаговМеньшеБольше
СкоростьМедленнееБыстрее
РефакторингЛегчеРискованнее

Пример в контексте ML модели

# Тест (RED)
def test_model_prediction():
    model = MyModel()
    X = np.array([[1, 2], [3, 4], [5, 6]])
    
    predictions = model.predict(X)
    
    assert predictions.shape == (3,)  # один предсказание на вход
    assert np.all((predictions >= 0) & (predictions <= 1))  # вероятности

# Код (GREEN)
class MyModel:
    def predict(self, X):
        return np.random.random(len(X))

# REFACTOR
class MyModel:
    def __init__(self, weights=None):
        self.weights = weights or np.random.randn(2)
    
    def predict(self, X):
        logits = X @ self.weights
        return 1 / (1 + np.exp(-logits))  # sigmoid
    
    def train(self, X, y, epochs=100):
        for _ in range(epochs):
            predictions = self.predict(X)
            loss = -np.mean(y * np.log(predictions + 1e-10) + 
                           (1-y) * np.log(1-predictions + 1e-10))
            # gradient descent...

Best Practices

# 1. Один assert на тест (или группируй логичные)
def test_string_methods():
    assert "hello".upper() == "HELLO"
    assert "hello".capitalize() == "Hello"

# 2. Названия тестов описывают поведение
def test_add_positive_numbers():
    assert add(2, 3) == 5

def test_add_negative_numbers():
    assert add(-2, -3) == -5

# 3. Используй pytest fixtures
@pytest.fixture
def sample_data():
    return pd.DataFrame({'a': [1, 2, 3]})

def test_with_fixture(sample_data):
    assert len(sample_data) == 3

# 4. Тестируй edge cases
def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

Резюме

TDD:

  • Пишем тесты ДО кода
  • Цикл: RED → GREEN → REFACTOR
  • Меньше багов, лучший дизайн
  • Требует дисциплины и практики
  • Ideal для production кода, менее ideal для research