Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Legacy Code: Seams и внедрение тестов
legacy_seams_injection

Legacy Code: Seams и внедрение тестов

Как внедрить тесты в непроверяемый код. Dependency injection для legacy. Швы и точки расширения

Seam (шов) — место в коде, где можно изменить поведение без изменения самого кода. Это позволяет внедрить тесты в непроверяемый legacy-код.

Цель: Научиться находить и создавать швы для тестирования legacy-кода.

Проблема: непроверяемый код

# legacy/order.py import smtplib from datetime import datetime class OrderProcessor: def process_order(self, order_id, customer_email): """Обработать заказ (непроверяемый!)""" # ❌ Прямой вызов БД conn = psycopg2.connect("postgresql://prod:5432/db") cur = conn.cursor() cur.execute("UPDATE orders SET status='processed' WHERE id=%s", (order_id,)) conn.commit() # ❌ Прямая отправка email smtp = smtplib.SMTP("smtp.gmail.com", 587) smtp.send_message(f"Order {order_id} processed", to=customer_email) # ❌ Текущее время (недетерминированно) timestamp = datetime.now() # ❌ Внешний API response = requests.post("https://analytics.com/track", json={"order": order_id}) return f"Processed at {timestamp}"

Проблемы:

  • ❌ Нельзя протестировать без production БД
  • ❌ Отправляет реальные emails
  • ❌ Зависит от текущего времени
  • ❌ Вызывает внешний API

Типы швов (Seams)

1. Object Seam: Dependency Injection

До (плохо):

class OrderProcessor: def process_order(self, order_id): # ❌ Hardcoded зависимость conn = psycopg2.connect("postgresql://prod:5432/db") # ...

После (хорошо):

class OrderProcessor: def __init__(self, db_connection): # ✅ Зависимость внедряется извне self.db = db_connection def process_order(self, order_id): cur = self.db.cursor() cur.execute("UPDATE orders SET status='processed' WHERE id=%s", (order_id,)) self.db.commit()

Тест:

def test_process_order(): """Тест с mock БД""" mock_db = Mock() mock_cursor = Mock() mock_db.cursor.return_value = mock_cursor processor = OrderProcessor(mock_db) processor.process_order(123) mock_cursor.execute.assert_called_once_with( "UPDATE orders SET status='processed' WHERE id=%s", (123,) )

2. Function Seam: Выделить в параметр

До:

def process_order(self, order_id): timestamp = datetime.now() # ❌ Недетерминированно return f"Processed at {timestamp}"

После:

def process_order(self, order_id, current_time=None): if current_time is None: current_time = datetime.now() # ✅ Default поведение return f"Processed at {current_time}"

Тест:

def test_process_order_timestamp(): """Тест с фиксированным временем""" fixed_time = datetime(2025, 1, 1, 12, 0, 0) result = process_order(123, current_time=fixed_time) assert result == "Processed at 2025-01-01 12:00:00"

3. Module Seam: Monkey Patching

Legacy код (не меняем):

# legacy/notifier.py import smtplib def send_notification(email, message): """Отправить email (legacy)""" smtp = smtplib.SMTP("smtp.gmail.com", 587) smtp.send_message(message, to=email)

Тест с monkey patch:

def test_send_notification(monkeypatch): """Тест с подменой smtplib""" mock_smtp = Mock() mock_smtp_instance = Mock() mock_smtp.return_value = mock_smtp_instance # Подмена модуля monkeypatch.setattr("smtplib.SMTP", mock_smtp) send_notification("test@test.com", "Hello") # Проверка что SMTP вызван mock_smtp.assert_called_once_with("smtp.gmail.com", 587) mock_smtp_instance.send_message.assert_called_once()

4. Environment Seam: Переменные окружения

Legacy код:

import os def get_api_key(): """Получить API ключ из окружения""" return os.getenv("API_KEY", "default-key")

Тест:

def test_get_api_key(monkeypatch): """Тест с подменой окружения""" monkeypatch.setenv("API_KEY", "test-key-123") result = get_api_key() assert result == "test-key-123"

Рефакторинг для тестируемости

Шаг 1: Извлечь зависимости

До:

class PaymentProcessor: def charge(self, amount, card_number): # ❌ Прямой вызов Stripe stripe.Charge.create(amount=amount, source=card_number)

После:

class PaymentProcessor: def __init__(self, payment_gateway): self.gateway = payment_gateway # ✅ DI def charge(self, amount, card_number): self.gateway.charge(amount, card_number)

Шаг 2: Создать интерфейс

# interfaces/payment.py from abc import ABC, abstractmethod class PaymentGateway(ABC): @abstractmethod def charge(self, amount, card_number): pass # Production implementation class StripeGateway(PaymentGateway): def charge(self, amount, card_number): stripe.Charge.create(amount=amount, source=card_number) # Test implementation class MockGateway(PaymentGateway): def __init__(self): self.charges = [] def charge(self, amount, card_number): self.charges.append({"amount": amount, "card": card_number})

Шаг 3: Тест

def test_payment_processing(): """Тест с mock gateway""" mock_gateway = MockGateway() processor = PaymentProcessor(mock_gateway) processor.charge(100.0, "4242424242424242") assert len(mock_gateway.charges) == 1 assert mock_gateway.charges[0]["amount"] == 100.0

Практика: Рефакторинг непроверяемого класса

# legacy/email_service.py import smtplib import logging from datetime import datetime class EmailService: def send_welcome_email(self, user_email, user_name): """Отправить welcome email""" # ❌ Hardcoded зависимости smtp = smtplib.SMTP("smtp.gmail.com", 587) logger = logging.getLogger() message = f"Welcome {user_name}!" timestamp = datetime.now() try: smtp.send_message(message, to=user_email) logger.info(f"Email sent to {user_email} at {timestamp}") return True except Exception as e: logger.error(f"Failed to send email: {e}") return False

Задание: Рефакторить для тестируемости:

  1. Извлечь smtp в параметр
  2. Извлечь logger в параметр
  3. Извлечь timestamp в параметр
  4. Написать тест с моками

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: Legacy Code: Strangler Fig Pattern