Тестирование
Тестирование в Python держится на двух вещах, которые кандидаты обычно знают по отдельности и почти никогда — вместе. Первая — модель того, сколько каких тестов держать и что именно остаётся настоящим на каждом ярусе. Вторая — механика pytest и unittest.mock, где почти всё интересное происходит не в момент прогона, а раньше — параметризация разворачивается на этапе сбора тестов, фикстура подставляется по имени параметра, а patch подменяет имя в пространстве имён одного конкретного модуля.
Питон-специфика здесь целиком про имена и время. Строка from x import y создаёт в вашем модуле собственную ссылку на объект — и патч x.y её уже не заденет, отсюда правило «патчить там, где имя ищется, а не где определено». Mock по умолчанию отвечает на любой атрибут новым моком, поэтому проверка без префикса assert_ не падает, а молча возвращает истинный объект. Аргумент по умолчанию вычисляется один раз при выполнении def, поэтому внедрение через client=HttpClient() даёт один общий экземпляр на весь процесс. А область видимости фикстуры — не деталь конфигурации, а решение о том, сколько тестов делят одно изменяемое состояние. Каждый из этих механизмов разобран в слоях ниже.
Карта темы
- Пирамида тестирования — модель состава набора по количеству и цене прогона, а не по важности проверок.
- Юнит и интеграционные тесты — граница проходит по зависимостям; здесь же механика
pytest— сбор, фикстуры, области видимости, параметризация. - Моки и patch — что именно заменяют дублёры, как устроен
Mockи почему цель патча ставят там, где имя ищется. - Внедрение зависимостей — шов, заложенный в дизайн — конструктор, параметр,
Protocol— против внешнегоpatch. - Линтеры и статический анализ —
flake8,pylint,ruff,black,mypy— что каждый реально проверяет и чего не доказывает.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Называть тест юнитом, когда он ходит в реальную БД или сеть | Прогон становится медленным и нестабильным, а падение больше не указывает на конкретный модуль |
Ставить цель patch там, где зависимость определена, а не где она ищется | Патч ложится на чужое пространство имён, тестируемый код продолжает звать настоящую зависимость |
| Проверять поведение самого мока вместо тестируемого кода | Тест доказывает, что Mock вернул заданное ему значение, и зеленеет при полностью сломанной логике |
Писать mock.called_once_with(...) без префикса assert_ | Mock создаёт атрибут на лету, вызов возвращает мок — проверка проходит всегда |
Держать изменяемое состояние в фикстуре со scope="session" | Тесты начинают зависеть от порядка запуска — падают в наборе и проходят поодиночке |
| Мокать зависимости внутри интеграционного теста | Проверять становится нечего — исчезает ровно та связка, ради которой тест и писался |
| Переворачивать пирамиду в пользу end-to-end тестов | Обратная связь измеряется минутами, причину падения ищут по всей системе, красный CI перестают читать |
Считать чистый прогон mypy и pylint доказательством корректности | Статический анализ не выполняет код и не знает значений — он не заменяет поведенческие тесты |
Значение для собеседований
Тему редко делают основной — её спрашивают ближе к концу секции, чтобы понять, писали ли вы тесты руками или только читали о них. Первый вопрос почти всегда одинаков — чем юнит отличается от интеграционного. Ответ «юнит меньше» засчитывают неохотно; ждут формулировки через зависимости — в юните все соавторы заменены, поэтому тест детерминирован и падение указывает на один модуль, а интеграционный намеренно оставляет настоящими БД, HTTP-клиент или соседний сервис и ловит проблемы связки. Сразу следом идёт пирамида, и здесь проверяют, понимаете ли вы, что её ось — количество и цена прогона, а не значимость проверок.
Дальше разговор уходит в механику. Просят объяснить, что такое мок и что он заменяет, и почти всегда добавляют вопрос про patch — где ставить цель. Правильный ответ звучит одной фразой — там, где имя ищется, а не где определено, — и подкрепляется механикой импорта, а не заученным правилом. Senior-уровень отличают вопросом когда внедрение лучше patch, и там ждут не лозунга, а различия — внедрение это шов в самом дизайне, устойчивый к переименованию модуля, а patch привязан к строковому пути и ломается при рефакторинге. Типичная провальная связка выглядит так — кандидат мокает всё подряд, включая то, что и составляет предмет проверки, и на вопрос «что именно доказывает этот тест» ответить не может. Отдельно спрашивают про pytest руками — фикстуры и их области, параметризацию — и про линтеры, где ключевой ответ в том, что статический анализ и тесты закрывают разные классы дефектов.