Получить доступ

Тестирование, логирование и базовые паттерны

Unit/integration тесты, уровни логирования, MDC и Singleton без ловушек.

Unit и integration tests

Unit-тест проверяет маленькую единицу логики изолированно, быстро и детерминированно. Integration-тест проверяет связку компонентов: БД, брокер, HTTP, Spring context.

Пирамида тестов: много быстрых unit-тестов в основании, меньше integration-тестов посередине и совсем немного медленных end-to-end сверху. Перевёрнутая пирамида (всё гоняется через медленные e2e) делает прогон долгим и хрупким.

Структура теста — AAA (Arrange-Act-Assert): подготовьте данные, выполните одно действие, проверьте один результат. Моки заменяют зависимости, чтобы изолировать логику, но мокать стоит свои границы (порты/интерфейсы), а не чужие библиотеки. Хороший набор тестов не мокает всё подряд: контракты с БД, сериализацией и брокером лучше периодически проверять на реальных адаптерах или Testcontainers — иначе тесты зелёные, а интеграция сломана.

Логирование

TRACE и DEBUG — детальная диагностика, INFO — важные штатные события, WARN — подозрительная ситуация, ERROR — сбой, который требует внимания.

В production логи должны помогать расследовать инцидент: correlation id, user/request id и структурированные поля полезнее длинных неструктурированных строк.

log.info("order created: orderId={}, userId={}", orderId, userId);
// не логируйте пароли, токены и персональные данные без необходимости

Singleton

Singleton гарантирует один экземпляр и глобальную точку доступа. Потокобезопасные варианты: enum singleton (самый простой и защищённый, в т.ч. от рефлексии и сериализации), static holder (ленивость через гарантию инициализации класса JVM) или double-checked locking с volatile.

Почему в double-checked locking поле обязано быть volatile — любимый senior-вопрос. instance = new Singleton() не атомарна: это (1) выделить память, (2) вызвать конструктор, (3) присвоить ссылку. Без volatile JVM/процессор вправе переставить (3) перед (2), и другой поток, увидев ненулевую ссылку на первой проверке (вне synchronized), получит наполовину сконструированный объект (half-constructed object) с полями по умолчанию. volatile запрещает такую перестановку и даёт happens-before между записью и чтением ссылки.

Главная ловушка дизайна — глобальное изменяемое состояние. В приложениях со Spring чаще используют singleton scope контейнера и внедрение зависимостей, а не ручной Singleton.

// enum — самый надёжный singleton
enum AppClock {
    INSTANCE;
    Instant now() { return Instant.now(); }
}

// double-checked locking: volatile обязателен
class Config {
    private static volatile Config instance;
    static Config get() {
        if (instance == null) {                 // без блокировки
            synchronized (Config.class) {
                if (instance == null) {          // под блокировкой
                    instance = new Config();
                }
            }
        }
        return instance;
    }
}

Как отвечать коротко

На интервью начинайте с определения, затем называйте trade-off и типичную ошибку. Такой формат хорошо работает для паттернов, логирования и тестов.

Например: «Unit быстрее и изолированнее, integration дороже, но ловит проблемы wiring/контрактов; ошибка — поднимать весь Spring context для простой функции».

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем