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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

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

Legacy Code: Characterization Tests

Как тестировать код, который не понимаешь. Документируем поведение через тесты. Фундамент работы с legacy

Legacy-код — это код без тестов, который страшно менять. Characterization Tests документируют текущее поведение кода через тесты.

Цель: Научиться покрывать тестами код, который вы не понимаете, чтобы безопасно его менять.

Проблема: legacy-код без тестов

Пример: модуль скидок

# legacy/discount.py def calculate_discount(price, customer_type, items_count, day_of_week): """Рассчитать скидку (никто не знает как работает)""" if customer_type == "gold": discount = 0.2 if items_count > 10: discount = 0.25 if day_of_week == "friday": discount = 0.3 elif customer_type == "silver": discount = 0.1 if items_count > 5: discount = 0.15 else: discount = 0 if items_count > 20: discount = 0.05 return price * (1 - discount)

Проблемы:

  • ❌ Никто не понимает всю логику
  • ❌ Код написан 3 года назад автором, который уволился
  • ❌ Нужно добавить новый customer_type="platinum"
  • ❌ Боитесь сломать существующую логику

Что такое Characterization Test?

Characterization Test — тест, который документирует фактическое поведение кода, даже если вы не понимаете почему оно такое.

Цель: Зафиксировать текущее поведение, чтобы потом безопасно рефакторить.

Шаг 1: Захватить все входы и выходы

# tests/test_discount_characterization.py import pytest from legacy.discount import calculate_discount # Захватываем ВСЕ возможные комбинации @pytest.mark.parametrize("price,customer_type,items,day,expected", [ # Gold customer (100, "gold", 5, "monday", 80.0), # 20% discount (100, "gold", 15, "monday", 75.0), # 25% discount (>10 items) (100, "gold", 5, "friday", 70.0), # 30% discount (friday) (100, "gold", 15, "friday", 70.0), # 30% discount (friday wins) # Silver customer (100, "silver", 3, "monday", 90.0), # 10% discount (100, "silver", 7, "monday", 85.0), # 15% discount (>5 items) (100, "silver", 7, "friday", 85.0), # Friday не влияет # Regular customer (100, "regular", 10, "monday", 100.0), # 0% discount (100, "regular", 25, "monday", 95.0), # 5% discount (>20 items) ]) def test_discount_characterization(price, customer_type, items, day, expected): """Characterization test: документируем текущее поведение""" result = calculate_discount(price, customer_type, items, day) assert result == expected

Шаг 2: Запустить и зафиксировать

pytest tests/test_discount_characterization.py -v # Результат: # tests/test_discount_characterization.py::test_discount_characterization[100-gold-5-monday-80.0] PASSED # tests/test_discount_characterization.py::test_discount_characterization[100-gold-15-monday-75.0] PASSED # ... # ✅ Все тесты зелёные — поведение зафиксировано!

Шаг 3: Теперь можно рефакторить

# legacy/discount.py (REFACTORED) def calculate_discount(price, customer_type, items_count, day_of_week): """Рассчитать скидку (рефакторинг)""" discount_rules = { "gold": {"base": 0.2, "bulk": (10, 0.25), "friday": 0.3}, "silver": {"base": 0.1, "bulk": (5, 0.15)}, "regular": {"base": 0.0, "bulk": (20, 0.05)}, } rules = discount_rules.get(customer_type, {"base": 0.0}) # Friday скидка для gold if customer_type == "gold" and day_of_week == "friday": discount = rules["friday"] # Bulk скидка elif "bulk" in rules and items_count > rules["bulk"][0]: discount = rules["bulk"][1] else: discount = rules["base"] return price * (1 - discount)

Запускаем тесты снова:

pytest tests/test_discount_characterization.py -v # ✅ Все тесты зелёные — рефакторинг безопасный!

Паттерн: Golden Master Testing

Проблема: сложный вывод (JSON, HTML)

# legacy/report.py def generate_report(transactions): """Генерирует отчёт (сложная логика форматирования)""" report = [] total = sum(t["amount"] for t in transactions) report.append(f"Total Transactions: {len(transactions)}") report.append(f"Total Amount: ${total:.2f}") for t in transactions: report.append(f"{t['date']} - {t['description']}: ${t['amount']:.2f}") return "\n".join(report)

Golden Master Test

# tests/test_report_golden.py import pytest from legacy.report import generate_report def test_generate_report_golden_master(): """Golden master: сравнение с эталонным выводом""" transactions = [ {"date": "2025-01-01", "description": "Payment", "amount": 100.0}, {"date": "2025-01-02", "description": "Refund", "amount": -20.0}, ] result = generate_report(transactions) # Эталонный вывод (captured from current code) expected = """Total Transactions: 2 Total Amount: $80.00 2025-01-01 - Payment: $100.00 2025-01-02 - Refund: $-20.00""" assert result == expected

Альтернатива: snapshot testing

# tests/test_report_snapshot.py def test_generate_report_snapshot(snapshot): """Snapshot test: автоматическое сохранение вывода""" transactions = [ {"date": "2025-01-01", "description": "Payment", "amount": 100.0}, ] result = generate_report(transactions) # Первый запуск: pytest сохранит вывод в snapshots/ # Последующие запуски: сравнение с сохранённым snapshot.assert_match(result, "report_output.txt")

Стратегия: Approval Testing

Установка

pip install approvaltests

Пример

# tests/test_discount_approval.py from approvaltests import verify from legacy.discount import calculate_discount def test_discount_all_scenarios(): """Approval test: захватить все сценарии""" scenarios = [] for customer in ["gold", "silver", "regular"]: for items in [1, 10, 25]: for day in ["monday", "friday"]: result = calculate_discount(100, customer, items, day) scenarios.append(f"{customer} | {items} items | {day} | ${result:.2f}") output = "\n".join(scenarios) verify(output)

Первый запуск:

pytest tests/test_discount_approval.py # Откроется diff tool (meld, kdiff3, vimdiff) # Вы проверяете вывод и approve

Последующие запуски:

  • Если поведение изменилось → diff показывает изменения
  • Вы решаете: это баг или ожидаемое изменение

Когда использовать Characterization Tests?

✅ Используйте когда:

  1. Код без тестов — нужно добавить safety net
  2. Сложная логика — много условий, не понятно что делает
  3. Перед рефакторингом — зафиксировать текущее поведение
  4. Legacy автор ушёл — никто не знает как работает
  5. Страшно менять — боитесь сломать production

❌ НЕ используйте когда:

  1. Код простой — легче переписать чем тестировать
  2. Код заведомо неправильный — фиксировать баги не нужно
  3. Нет production usage — никто не использует этот код

Практика: Покройте legacy-функцию

Задание

# legacy/tax.py def calculate_tax(income, country, dependents, tax_year): """Рассчитать налог (сложная логика)""" if country == "US": if income < 10000: rate = 0.1 elif income < 40000: rate = 0.12 else: rate = 0.22 if dependents > 0: deduction = dependents * 2000 if tax_year == 2024: rate -= 0.01 # Tax cut 2024 elif country == "UK": if income < 12500: rate = 0 elif income < 50000: rate = 0.2 else: rate = 0.4 deduction = 0 return income * rate - deduction

Ваша задача:

  1. Написать characterization tests для всех сценариев
  2. Найти баг в коде (hint: deduction не инициализирован для US)
  3. Зафиксировать текущее поведение (даже с багом!)
  4. Потом можно будет исправить безопасно

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

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

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

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