Питання 15 · Розділ 18

Як SOLID допомагає в тестуванні коду?

Принципи SOLID часто називають "Design for Testability". Без них Unit-тестування перетворюється на боротьбу з кодом, а не на перевірку логіки.

Мовні версії: English Russian Ukrainian

Глибоке занурення (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]]