Основы программирования
Тестирование: 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)
От 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