Допустим, mock зависимость принимает любой незапланированный вызов и возвращает значение по умолчанию. Како...

Допустим, mock-зависимость принимает любой незапланированный вызов и возвращает значение по умолчанию. Какой основной дефект unit-теста это скрывает?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Такой mock скрывает неожиданные взаимодействия с зависимостью. Тест может пройти, хотя production-код вызывает не тот метод, обращается к зависимости лишний раз или использует неподдерживаемый сценарий.

Исторический контекст

Mock-объекты появились как способ изолировать тестируемый компонент от внешних зависимостей и проверять их взаимодействие без базы данных, сети или файловой системы. На практике встречаются как строгие mocks с обязательными ожиданиями, так и permissive mocks, которые спокойно принимают незадекларированные вызовы.

Мягкое поведение удобно для быстрого написания тестов и уменьшает связанность с деталями реализации. Однако за это приходится платить снижением способности теста обнаруживать изменения контракта взаимодействия.

Постановка проблемы

Если mock возвращает значение по умолчанию для любого вызова, отсутствие настройки не считается ошибкой. Поэтому тест проверяет только итоговый результат, но может не заметить, что компонент получил этот результат через неправильный путь.

Например, вместо обращения к кэшу код может незаметно обратиться к хранилищу, а permissive mock вернёт пустое значение или nil. Тест останется зелёным, хотя исчезла важная гарантия о взаимодействии с зависимостью.

Подробное решение

Поведение mock должно соответствовать цели теста. Если конкретный вызов является частью контракта компонента, его следует явно настроить и проверить: метод, существенные аргументы, число вызовов и возвращаемый результат.

Незапланированные вызовы должны приводить к ошибке хотя бы для тех методов и сценариев, которые тест должен контролировать. Это можно реализовать строгим mock, ручным fake с ошибкой по умолчанию или явной проверкой записанных вызовов после выполнения теста.

Не следует проверять каждое внутреннее взаимодействие автоматически. Проверка несущественного порядка вызовов или количества технических обращений делает тест хрупким. Компромисс состоит в том, чтобы строго проверять только наблюдаемые гарантии контракта, а детали реализации оставлять свободными.

Важно отличать permissive mock от fake, который намеренно моделирует допустимое поведение зависимости. Fake может не проверять конкретные вызовы, но тогда тест должен явно проверять результат и свойства модели; случайное принятие любого вызова без ограничений обычно является пробелом в проверке.

Ситуация из практики

Сервис должен сначала проверить локальный кэш, а при отсутствии значения обратиться к хранилищу. В тесте mock хранилища принимал любые вызовы и возвращал пустой результат по умолчанию. После рефакторинга сервис начал обращаться к хранилищу напрямую, но тест продолжил проходить.

Рассматривались три варианта. Полностью строгий mock хорошо обнаруживал неожиданные обращения, но делал тесты чувствительными к несущественным изменениям; permissive mock был простым, но пропускал дефект; ручной fake позволял проверить сценарий кэша, однако требовал отдельной модели состояния.

Выбрали ручной fake для проверки поведения кэша и отдельную проверку отсутствия обращения к хранилищу в сценарии попадания в кэш. В сценарии промаха вызов хранилища проверялся явно. В результате тесты обнаруживали нарушение маршрутизации, но не фиксировали внутренние детали, не являющиеся частью контракта.

Что кандидаты часто упускают

1. Достаточно ли проверить только число вызовов mock?

Нет. Правильное число вызовов не гарантирует правильность аргументов и порядка значимых операций. Проверять нужно те свойства взаимодействия, которые влияют на контракт: идентификатор, переданные параметры, обработку ошибки и допустимость самого вызова.

2. Всегда ли строгий mock лучше permissive mock?

Нет. Строгий mock повышает чувствительность к неожиданным взаимодействиям, но может связать тест с реализацией и затруднить рефакторинг. Если контракт выражается через результат или состояние, fake либо проверка результата часто надёжнее, чем полный список внутренних вызовов.

3. Может ли permissive mock быть оправдан?

Да, если незадекларированные вызовы заведомо несущественны для конкретного теста и их результат не влияет на проверяемый контракт. Но это решение должно быть осознанным: существенные методы и ошибки необходимо ограничивать явно, иначе зелёный тест нельзя считать доказательством корректного взаимодействия.