Основы программирования

Тестирование: unittest, моки и TDD

В Google каждый разработчик обязан покрывать код тестами. В Facebook деплой происходит автоматически - только тесты останавливают плохой код. Без тестов любой рефакторинг - это лотерея.

  • CI/CD: GitHub Actions запускает тесты при каждом push - тест = ворота для деплоя
  • Регрессии: Airbnb потеряла миллионы из-за бага в оплате, который покрывался бы unit-тестом
  • TDD в embedded: NASA использует TDD для кода космических зондов
  • pytest: стандарт Python-тестирования в Netflix, Dropbox, Mozilla

Предварительные знания

  • Уметь писать функции с явными параметрами и возвращаемым значением (тестируемый код)
  • Базовое понимание исключений: try/except, raise, типы ошибок
  • Установленный pip и опыт работы с виртуальным окружением (venv, uv, poetry)
  • Functions
  • Refactoring: Improving Code Without Changing Behavior

От SUnit Кента Бека до Jest, Pytest и property-based тестирования

Идея автоматизированных unit-тестов появилась задолго до xUnit. В 1983 году Борис Байзер выпустил Software Testing Techniques, классический учебник по тестовым техникам: эквивалентные классы, граничные значения, decision tables. В 1989 году Кент Бек написал SUnit для Smalltalk, первый фреймворк семейства xUnit с двумя ключевыми идеями: тест это маленький класс, и assert это просто метод. В 1997 году Бек вместе с Эрихом Гаммой (одним из GoF) в самолёте по дороге на OOPSLA портировал SUnit в JUnit для Java. JUnit стал шаблоном для NUnit, PyUnit (Python unittest), PHPUnit и десятков других. В 2002 году Бек выпустил Test-Driven Development by Example и сформулировал цикл Red-Green-Refactor; TDD стал одной из 12 практик Extreme Programming. В 1999 году Джон Хьюз и Коен Классен в Чалмерсе опубликовали QuickCheck для Haskell: вместо примеров тестировщик описывает свойства, а библиотека сама генерирует входы и сжимает контрпример при падении. Эту идею потом скопировали Hypothesis для Python (Дэвид Маклевер, 2013), ScalaCheck, fast-check для JS. В 2003 году Хольгер Крекель начал py.test, который к 2010-м обогнал unittest по популярности благодаря фикстурам, parametrize и человечным сообщениям об ошибках. В 2005 году Стивен Бейкер выпустил RSpec для Ruby и популяризовал BDD-стиль describe/it. В 2014 году Кристоф Поджер в Facebook выпустил Jest для тестов React-компонентов: snapshot testing и параллельный запуск стали стандартом для JavaScript. К 2025 году типичный Python-стек это pytest + Hypothesis + pytest-asyncio + coverage.py.

Unit-тесты: тестируем одну вещь за раз

**Unit-тест** проверяет одну функцию или метод в изоляции от остального кода. Хороший тест: быстрый, независимый, повторяемый, самопроверяющийся, своевременный (FIRST).

**pytest.approx()** нужен для сравнения float. Никогда не пиши `assert 0.1 + 0.2 == 0.3` - это ложь из-за погрешности представления с плавающей точкой! Используй `pytest.approx(0.3)` или `abs(result - 0.3) < 1e-9`.

Что должен делать хороший unit-тест?

Моки: изолируем внешние зависимости

**Мок (Mock)** - объект-заглушка, имитирующий поведение реальной зависимости (API, БД, email-сервис). Без моков тест отправлял бы настоящие письма или менял реальные данные.

Зачем использовать мок вместо реального объекта в тесте?

TDD: сначала тест, потом код

**Test-Driven Development** - цикл Red → Green → Refactor: 1) Напиши падающий тест (Red). 2) Напиши минимальный код для прохождения теста (Green). 3) Улучши код (Refactor). Повторяй.

TDD = «100% покрытие». Сначала пишем все тесты, потом весь код. Если покрытие меньше 100% - проект непрофессиональный.

TDD - это цикл Red-Green-Refactor с шагом в один тест за раз, а не «сначала все тесты». Тесты пишутся итеративно, под текущую микро-задачу. Покрытие 100% - не цель, а побочный продукт: важно покрывать поведение, а не строки кода. 70-80% значимого покрытия лучше, чем 100% мусорного.

TDD путают с test-first-development и метрикой coverage. На деле Кент Бек описывает короткие циклы (5-10 минут): один тест, минимальный код, рефакторинг. Coverage 100% часто достигается за счёт getter/setter тестов, которые ничего не ловят. Реальная польза TDD - в дизайне (тестируемый код = слабая связанность), а не в проценте покрытия.

В цикле TDD, что означает 'Red' фаза?

Ключевые идеи

  • Unit-тест: одна функция, паттерн AAA (Arrange-Act-Assert)
  • pytest.approx() для float-сравнений
  • Мок: заглушка зависимости. MagicMock, assert_called_once_with()
  • TDD цикл: Red (тест падает) → Green (минимальный код) → Refactor
  • pytest.mark.parametrize: один тест = много входных данных

Связанные темы

Тестирование и рефакторинг неразделимы:

  • Рефакторинг — Тесты - страховка при рефакторинге
  • Функции — Хорошие функции (одна ответственность) легко тестировать

Вопросы для размышления

  • Как протестировать функцию, зависящую от текущего времени datetime.now()? Что нужно мокнуть?
  • В чём разница между mock, stub и spy? Когда применять каждый?
  • Почему тест, который всегда проходит - это антипаттерн? Как убедиться, что тест действительно проверяет поведение?

Связанные уроки

  • prog-14-refactoring — Тесты - предусловие безопасного рефакторинга
  • prog-13-patterns — Test patterns (AAA, fixtures) - паттерны для тестового кода
  • stat-11-bayesian — Hypothesis testing в статистике - та же логика что unit test
  • prob-01-intro — Вероятность ошибки в production - статистика покрытия тестов
  • devops-01
Тестирование: unittest, моки и TDD

0

1

Войти