Косвенное тестирование (Indirect Testing)
Тест обращается к целевому объекту не напрямую, а через фасад, обёртку или другой объект. Причинно-следственная связь между действием теста и проверяемым компонентом неочевидна.
Симптомы
- Тест
OrderServiceTestвызываетappFacade.processOrder(...)вместоorderService.calculateTotal(...) - Используется «удобная» утилита-обёртка вместо тестируемого класса напрямую
- При падении непонятно, в каком именно компоненте за фасадом произошла ошибка
- Тест-класс называется
XxxTest, но создаёт и вызываетApplicationFacadeилиServiceLocator
Причины возникновения
- Фасад удобен для вызова, а прямой класс требует настройки зависимостей
- Нет DI-контейнера или фабрики — сложно создать
OrderServiceс нужными зависимостями вручную - Тест писался «по аналогии» с интеграционным тестом, а не как unit-тест
- Незнание паттернов stub/mock для изоляции зависимостей
Почему это проблема
- Неясная диагностика: фасад может делегировать в 5 разных сервисов — при падении неизвестно, кто виноват
- Избыточная связанность: тест зависит от реализации фасада, а не от контракта целевого класса
- Слабая изоляция: изменение несвязанного компонента за фасадом ломает «тест OrderService»
- Маскировка дефектов: фасад может компенсировать баг в сервисе, и тест не обнаружит проблему
Решение
Тест работает напрямую с тестируемым классом, зависимости изолированы через моки:
// ❌ через фасад — неясно, что именно тестируется
BigDecimal total = appFacade.processOrder("user1", "product1", 3);
assertEquals(new BigDecimal("300.00"), total);
// ✅ напрямую с тестируемым классом
OrderService orderService = new OrderService(priceRepository);
Order order = new Order("product1", 3, new BigDecimal("100.00"));
BigDecimal total = orderService.calculateTotal(order);
assertThat(total).isEqualByComparingTo("300.00");