Все вірно. Є трейдофи, головне знати про різні варіанти.
Не обов’язково. Якщо винести бізнес-логіку в окремі компоненти, то їх можна тестувати без моків.
Тобто ви, звісно, можете написати пару тестів з моками. Але при цьому тестувати компоненту, котра не має зовнішніх залежностей значно простіше.
А чим спрінг завинив? Якщо ви конкретно про Dependency Injection — то він дуже спрощує роботу з ініціалізацією проекту. Тобто це просто інструмент, щоб руками об’єкти в конструктори передавати.
Якщо відповідати на питання — виносите бізнес логіку в окремі компоненти і тестуєте їх без моків.
Це цікава ідея. Але ні — інтерв’ювер хотів почути про «мок» про що сам мені зізнався. Я не думаю, що він би від мене це приховував.
Якщо контролери-сервіси-даошки мають однорядочкові методи, котрі просто викликають одне одного і перетягують дані з бази на UI — тоді так, змісту з юніт-тестів небагато. Але щойно з’являється щось складніше — хоча б якась бізнес логіка (перевірка на консистентність переданих даних, якась особлива фільтрація чи сортування, перемаплення не 1-в-1 а щось поскладніше) — то вже можна юніт-тестувати. При чому по можливості треба переносити цю бізнес-логіку в окремі компоненти, котрі не мають зовнішніх залежностей і приклад цього поданий в статті (там де тестується OrderStatisticsNotificator).
Ключова фіча моків — це можливість перевірки взаємодії/інтеракції з об’єктом.
Так, але, на жаль, часто моки використовують там, де їх використовувати не треба. Цитуючи вас же ж:
Просте правило: ми мокаємо залежності юніта, який ми тестуємо. Тут складність визначити границі того юніта і що є залежністю, а що деталлю реалізації.
і ось тут часто виникають проблеми, тому що мокають взагалі все, окрім лише класу, котрий тестують. Тобто мокають усі залежності — навіть якщо їх можна просто інстанціювати, мокають статичні методи, і навіть з моків вертають не ДТОшки, а моки ДТОшок, в котрих мокають гетери. І все через нерозуміння того, що мокати треба, а що ні. І в таких тестах можна забрати більшість моків і тест від цього стане лише кращим.
Уникати тоді, коли цей ретурн неочевидний. Наприклад коли метод на 40 рядочків, а тут неждано-негадано ретурн на 23му.
Не зовсім так. «Перше, що знайду — те і буду використовувати» — це лише один зі способів вибору компонента. Чи робити саме так — залежить від фреймворку і потреб.
Наприклад так можна робити, коли шукається проганяч тестів (test runner) для JUnit5. Ніхто ж не очікує, що при двох наявних реалізаціях в classpath, тести проганятимуться теж двічі. Але в тому ж випадку можна викинути помилку — логіка вибору компоненту самим SPI не задається, і JUnit5 міг би відмовитися проганяти тести при наявних двох реалізаціях (я, чесно, не перевіряв, що він зробить).
Інтерфейс, який шукають, може оголосити метод .priority(). І тоді після того, як завантажаться всі реалізації, можна посортувати по пріорітету і взяти з найвищим.
Мало того, можна обрати декілька імплементацій. Саме так відбувається з javax.servlet.ServletContainerInitializer — саме тому в одному контейнері сервлетів може існувати паралельно і SpringMVC і Jersey — обоє оголошують реалізацію javax.servlet.ServletContainerInitializer і контейнер сервлетів ініціалізує обидва.
Але зазвичай ми самі рідко контролюємо логіку підбору компонентів, адже фреймворки ми пишемо рідко. І відповідно ми залежимо від того, як SPI реалізували самі фреймворки. Те, що вони роблять, і справді часто виглядає, як магія — тут я погоджуюся.
В Кента Бека досить адекватне бачення TDD, до речі. Ось цитата з його інтерв’ю:
“If you’re in exploration mode and you’re just trying to figure out what a program might do and most of your experiments are going to be failures and be deleted in a matter of hours or perhaps days, then most of the benefits of TDD don’t kick in, and it slows down the experimentation”
Залежить від того, хто шукає драйвер. По-перше драйвер можна взагалі не шукати, просто беремо потрібний драйвер, ініціалізуємо його за допомогою new і працюємо надалі з ним.
Далі все залежить від фреймворка. Наприклад є фреймворки, де правильний драйвер задається прямо в конфігураційних файлах — наприклад Hibernate дійсно може шукати назву драйвера в якісь
Але якщо драйвер явно не вказується, тоді пошук може відбуватися (і часто таки відбувається) за допомогою SPI. Тобто вантажаться усі класи, вказані в усіх ресурсах під іменем
META-INF/services/java.sql.Driver.
driver.acceptsUrl
Для того, щоб спитати в ChatGPT «Що таке SPI в Java?» для початку треба знати, що SPI в джаві взагалі існує.
Зараз існує GraalVM, там теж є AOT (Ahead of Time) компілятор, що компілює безпосередньо в машинний код.
Чесно, випадково вийшло. Як справжнього джавіста мене мало цікавлять низькорівневі інтерфейси, тому була відустня стійка асоціація абревіатури SPI з чимось окрім як джавішної технології.
Спробувати можна. Наприклад ви побачили якийсь дуже складний приватний метод, але аж раптом — він зовнішні залежності не викликає — це вже один кандидат. Або можна спробувати спочатку зробити виклики зовнішніх сервісів, а потім вже пробувати обробити всі результати разом. Можна взагалі зробити Lazy запит — тобто обгорнути його у функцію і викликати його лиш тоді, коли треба. В імперативному програмуванні воно виглядає неорганічно, але от наприклад в WebFlux це — звична річ — передати якийсь Mono у функцію. Відповідно цей Mono може і не викликатися, а протестувати метод можна без моків, бо замість справжнього моно можна передати Mono.just().
Я не кажу, що це завжди вийде, але якщо пробувати побачити такі місця, то їх виявляється більше, ніж спочатку очікувалося.