Як SOLID допомагає в тестуванні коду?
Принципи SOLID часто називають "Design for Testability". Без них Unit-тестування перетворюється на боротьбу з кодом, а не на перевірку логіки.
Глибоке занурення (Under the Hood)
Принципи SOLID часто називають “Design for Testability”. Без них Unit-тестування перетворюється на боротьбу з кодом, а не на перевірку логіки.
Senior-інсайт: Прякий зв’язок принципів і тестів
1. SRP: Ізоляція тестів
Якщо у класу одна відповідальність, вам потрібен один тестовий клас.
- Без SRP: Щоб протестувати розрахунок знижки, вам доводиться мокати БД, поштовий сервіс і кеш.
- З SRP: Ви тестуєте
DiscountCalculatorв чистому вигляді. Тести швидкі, зрозумілі і не падають через зміни в БД.
2. OCP: Захист існуючих тестів
При додаванні нової фічі ви пишете нові тести для нового класу.
- Вам не потрібно переписувати 100 старих тестів, оскільки ви не змінювали старий код. Це основа регресійної стабільності.
3. LSP: Гарантія коректності тестів
LSP (Liskov Substitution Principle — принцип підстановки Лісков: нащадки повинні бути замінені базовим типом) гарантує, що якщо тест проходить для базового класу, він зобов’язаний проходити для будь-якого нащадка.
- Senior Technique: “Abstract Test” патерн (базовий тестовий клас, який запускається для кожної реалізації інтерфейсу). Напишіть тести для інтерфейсу і запускайте їх для кожної реалізації. Якщо одна реалізація падає — вона порушує LSP.
4. ISP: Маленькі моки
ISP позбавляє від необхідності реалізовувати (або мокати) 20 методів інтерфейсу, коли вашому тесту потрібен тільки один. Це робить тести лаконічними.
5. DIP: Підміна реальності
Це “король” тестування. DIP дозволяє розірвати зв’язок з реальним світом (БД, мережа, час).
- За допомогою Dependency Injection (внедрення залежностей через конструктор) ви підсовуєте сервісу
MockOrderRepository(моковий об’єкт-заглушку, який повертає тестові дані замість підключення до реальної БД) замість справжнього. Ви можете імітувати будь-які помилки (Timeout, Network Down) і перевірити, як поведе себе код.
SOLID і TDD (Test Driven Development — розробка через тести: спочатку пишете тест, потім код)
TDD — один з найкращих способів зрозуміти SOLID на практиці.
- Якщо вам боляче писати тест для цього методу — значить, ви порушили SRP або DIP.
- Якщо ви не можете створити об’єкт в тесті без підняття всієї системи — значить, у вас проблеми з Coupling.
Продуктивність тестів
- Cold Start: SOLID дозволяє запускати тисячі тестів без підняття Spring Context. Тест на чистому JUnit з моками виконується за мілісекунди.
- Parallel Execution: Завдяки відсутності спільних mutable станів (DIP + SRP) тести, написані по SOLID, ідеально розпаралелюються на всі ядра CPU.
Діагностика якості архітектури через тести
- Mock Ratio: Якщо кількість рядків налаштування моків (
when(...).thenReturn(...)) перевищує кількість рядків самої перевірки логіки — архітектура переускладнена (порушення SRP). - Test Fragility: Якщо будь-яка зміна коду ламає 50 тестів в різних модулях — у вас сильна зв’язаність і порушення DIP.
Резюме для Senior
- Тестованість — це важливий побічний ефект хорошого дизайну. Але не єдиний: читабельність, продуктивність і простота теж важливі.
- Хочете перевірити свій код на SOLID? Спробуйте написати для нього Unit-тест без використання
SpringExtension(Spring-анотація, яка завантажує весь контекст додатку для теста — це інтеграційний, а не unit-тест). - DIP робить тести детермінованими (ви контролюєте навіть “поточний час” через ін’єкцію
Clock). - Пам’ятайте: код, який вкрай складно протестувати ізольовано — скоріше за все, має проблеми з дизайном. Але бувають винятки (legacy-код, низькорівневі утиліти).
Коли SOLID НЕ є головним пріоритетом для тестування
- Прототипи і spike-рішення: Швидкий код для перевірки гіпотези — моки і інтерфейси тільки сповільнять.
- Чисто функціональний код: Функції без побічних ефектів тестуються без SOLID — просто викличте з різними аргументами.
- Утиліти і helper-класи: Статичні методи на кшталт
StringUtils.join()не потребують моків і DIP. - Integration-тести: Коли ви цілеспрямовано тестуєте взаємодію з реальною БД — моки не потрібні, DIP не допомагає.
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- SOLID = “Design for Testability”: без SOLID unit-тестування перетворюється на боротьбу з кодом
- SRP: один тестовий клас, без моків БД/пошти/кешу для перевірки однієї логіки
- OCP: нові тести для нового класу, не потрібно переписувати 100 старих тестів
- LSP: тест для базового класу зобов’язаний проходити для будь-якого нащадка (Abstract Test патерн)
- ISP: маленькі моки — не потрібно мокати 20 методів, коли тесту потрібен один
- DIP: підміна реальності через Mock — король тестованості, контроль навіть “поточного часу” через
Clock - Mock Ratio: якщо рядків моків > рядків логіки — архітектура переускладнена
Часті уточнюючі запитання:
- Як перевірити SOLID без Spring? — Написати unit-тест без
SpringExtension; якщо не виходить — проблеми з Coupling - Що таке Abstract Test патерн? — Базовий тестовий клас для інтерфейсу, запускається для кожної реалізації
- Що таке Test Fragility? — Будь-яка зміна коду ламає 50 тестів в різних модулях = сильна зв’язаність
- Коли SOLID не потрібен для тестування? — Прототипи, чисто функціональний код, утиліти, integration-тести
Червоні прапори (НЕ говорити):
- “Тестованість — єдиний критерій хорошого дизайну” (читабельність, продуктивність теж важливі)
- “Потрібно завжди використовувати SpringExtension” (це інтеграційний, а не unit-тест)
- “Більше моків = кращий тест” (більше моків = більше coupling, крихкі тести)
Пов’язані теми:
- [[8. Що таке принцип Dependency Inversion]]
- [[16. Як принцип Dependency Inversion пов’язаний з Dependency Injection]]
- [[1. Що таке принцип Single Responsibility і як його застосовувати]]
- [[7. Що таке принцип Interface Segregation]]