Почему зависимости должны быть от абстракций, а не от конкретных реализаций?
Что проверяют
Понимаете ли вы принцип инверсии зависимостей и умеете ли применять его по-питоновски. В языке с утиной типизацией лишний слой абстракции легко превращается в ритуал, и интервьюер смотрит, отличаете ли вы нужную границу от выдуманной.
Как отвечать
Сформулируйте принцип: модуль с бизнес-логикой не зависит от модуля с деталями, оба зависят от абстракции. На практике это значит, что сервис не импортирует драйвер базы и HTTP-клиент, а принимает объект с нужным поведением аргументом.
class Notifier(Protocol):
def send(self, user_id: int, text: str) -> None: ...
class OrderService:
def __init__(self, notifier: Notifier) -> None:
self._notifier = notifierДальше про питонические средства. typing.Protocol задаёт структурный интерфейс: реализации от него не наследуются, достаточно совпадения сигнатур, поэтому подойдёт и класс из чужой библиотеки. abc.ABC с abstractmethod берут, когда нужно запретить создание неполной реализации и вынести общий код в базовый класс.
Выгоды называйте конкретно: тестовая реализация передаётся аргументом, а не патчем; направление импортов удерживает слои от перемешивания; смена очереди или хранилища не расползается по всему коду.
Где спотыкаются
Делают интерфейс на одну реализацию и называют это гибкостью. Абстракция оправдана, когда реализаций уже две или когда граница проходит по внешнему миру, то есть по сети, диску, времени и случайности. Интерфейс «на будущее» добавляет файл и не разделяет ничего.
Второе: пишут дырявую абстракцию. Репозиторий, который принимает кусок SQL или возвращает объект ORM с ленивыми связями, протаскивает базу в доменный слой прямо через сигнатуру, и подменить его в тестах уже нечем.
Третье: вместо внедрения зависимости патчат по строковому пути через unittest.mock.patch. Патчить приходится там, где имя используется, а не там, где оно определено, первый же переезд модуля ломает тест, и проверяет такой тест не поведение, а расположение импортов.