Какую часть многослойного приложения лучше покрывать юнит-тестами
Что проверяют
Понимаете ли вы, где в приложении живёт логика, которую выгодно проверять быстро и дёшево, и где юнит-тест превращается в проверку моков. Следом обычно спрашивают, как вы тестируете слой доступа к данным и что делаете с внешними API.
Как отвечать
Основную массу юнит-тестов пишите на слой бизнес-логики: сервисы, use case, доменные модели, валидацию, расчёты. Там принимаются решения и больше всего ветвлений, а при чистом коде нет ввода-вывода, поэтому тесты на pytest идут миллисекунды.
- доменный слой и сервисы: юнит-тесты с граничными случаями, удобно через
pytest.mark.parametrize. Зависимости (репозиторий, клиент внешнего API) передаются через конструктор или аргументы и подменяются фейком, например репозиторием в памяти на словаре; - слой доступа к данным: интеграционные тесты против той же СУБД, что в проде, например PostgreSQL в контейнере через
testcontainers. Мокать цепочкуsession.execute(...).scalars()бессмысленно: тест проверит только порядок вызовов; - веб-слой: должен быть тонким. Его проверяют несколькими тестами через
TestClientв FastAPI или тестовый клиент Django: коды ответов, валидация, авторизация. Подменять зависимости в FastAPI удобно черезapp.dependency_overrides.
Если архитектура близка к портам и адаптерам, ответ совсем простой: ядро покрываем юнитами, адаптеры интеграционными тестами.
Про покрытие: pytest-cov с --cov-branch показывает непроверенные ветки, но цель это проверенная логика, а не число в отчёте.
Где спотыкаются
Логика живёт во вьюхах Django или в эндпоинтах FastAPI, и юнит-тестировать оказывается нечего. Ответ «покрываю бизнес-логику» тогда звучит пусто: сначала логику выносят в сервисы.
Тестируют репозитории на SQLite в памяти вместо PostgreSQL. Отличаются типы, JSONB, блокировки и поведение транзакций, и тесты зелёные при сломанном проде.
Ставят mock.patch не туда. Патчить нужно имя там, где его ищут при вызове, то есть в модуле, который импортировал функцию, а не там, где она объявлена.